If you read Getting Started with Perl/Tk
and Scoreboard: A 15-Minute Perl/Tk Application,
you should be a seasoned Tk novice by now. Assuming so, let’s move right
along and examine the Perl/Tk implementation of the Mouse Odometer,
named modo
and pictured in Figure 36-1.
modo
has got to be one of the most pointless
programs ever written. But it illustrates numerous Perl/Tk features, so
it does have some value. I first saw similar code for the Mac, written
by Sean P. Nolan, that simply tracked a machine’s cursor. Currently I
have logged well over 51 kilometers on my cursor, and my mouse has
careened around its pad some 14 kilometers.
Most of this column is not about modo
and how
it works, but rather the Perl/Tk features it uses. This time we’ll
learn how to schedule asynchronous timer events, and look more closely
at window manager commands, menus, menubuttons, and the ColorEditor.
We’ll also create and explain in detail an object-oriented Perl/Tk
composite widget that we’ll create called the Odometer. Like a car’s
odometer, we want our mouse odometer to record the physical
distance traveled by the mouse, not the number of pixels.
In a car, you want to know how many miles you’ve traveled, not merely
the number of tire-lengths, which will vary from car to car. In the X
window system you can use the xdpyinfo
command to
find out the number of millimeters-per-pixel of your display, and
that, multiplied by a pixel count, gives the distance in millimeters.
Unfortunately, pixels aren’t always square, so there are actually two
numbers to worry about: the horizontal and vertical millimeter per
pixel ratios. Once we know those numbers, we can figure out the
distance D given the number of pixels traversed
in the X and Y directions, which we’ll call dX
and dY. In pseudocode:
D = sqrt((dX * ($mmX/$pixelsX)) ** 2 + (dY * ($mmY/$pixelsY)) ** 2); |
How can we figure out dX and
dY? Well, Tk provides the command
pointerxy
, which returns a two-element list: the
cursor’s absolute X and Y coordinates. (In deference to Einstein, who
taught us that nothing is absolute, we’ll say “relative to the root
window of your display.”) So if we call pointerxy
twice, we can subtract the results, yielding our
dX and dY. Then we can just
apply the above formula. (Which is thankfully just the Pythagorean
Theorem, since we’re dealing with a non-curved two-space. Otherwise we
might need Albert’s ten-dimensional tensors.)
The major components are a row of menubuttons (often called a
menu bar), two sets of odometers (one for the
cursor and one for the pointer), and a status line showing the
distance units and cursor coordinates. Here is
modo
’s main loop, with a tiny amount of code
dealing with pointing device distance removed for clarity:
sub modo { # Track the cursor forever, periodically updating the odometer file. my( $x, $y) = $MW->pointerxy; $W_MISC_TEXT = sprintf("U=%-3s (%4d,%4d)", $MODO_UNITS_HUMAN, $x, $y); my ($dx, $dy) = (abs($x - $LAST_X), abs($y - $LAST_Y)); ($LAST_X, $LAST_Y) = ($x, $y); my ($dxmm,$dymm) = ($dx*$MM_PIXEL_X, $dy*$MM_PIXEL_Y); my $d = sqrt( ($dxmm * $dxmm) + ($dymm * $dymm) ); $W_CODO->add($d,$MODO_UNITS) if $d > 0; if ($AUTOSAVE_COUNT-- <= 0) { $AUTOSAVE_COUNT = $AUTOSAVE_TICKS; eval {save_modo}; } $MW->after($MILLISECOND_DELAY, &modo); }
Upon startup, modo
is called once, and
exactly once. The modo
subroutine performs several
tasks:
It fetches the pointer’s X/Y information and updates the
variable $W_MISC_TEXT
with the current display
units and cursor’s root window coordinates. This variable has been
specified as the -textvariable
option of the
label widget packed at the bottom of modo
’s
window. As you learned last time, changing a
-textvariable
updates the display
immediately.
It calculates the distance the cursor has moved, in millimeters.
It adds a non-zero distance to the cursor Odometer widget
$W_CODO
. Note that add
is a
method of class Odometer. Later we’ll see how the Odometer class
was created and why it behaves like a standard Perl/Tk
widget.
It periodically saves the distance data to a file so that
useless odometer data is not lost. The tick count is based on how
often you want the state information saved and the time interval
between invocations of modo
.
It reschedules itself via a call to
after
. There are several ways to invoke
after
, but in Tk-land the form shown above is
the most common. The first parameter is the delay time in
milliseconds after which the second parameter, a standard Tk
callback, is executed. Since this event is asynchronous
modo
returns and “pops the stack.” After the
delay the callback is invoked and modo
is
called once again, does its thing, reschedules itself, and
returns.
There are many aspects to designing and writing a robust
application, and one of them is to give the user adequate real-time
feedback so they know things are “working.” Since
modo
takes some time to start up, we’ll open a new
top-level window that displays its current initialization state, along
these lines:
$QUIT_COMMAND = sub {save_modo; exit}; $MW = MainWindow->new(-screen => $OPT{'display'}); $MW->withdraw; $MW->title($OPT{'title'}); $MW->iconname('modo'), $MW->iconbitmap("@$LIBDIR/icon.xbm"); $MW->minsize(50, 50); $MW->protocol('WM_DELETE_WINDOW' => $QUIT_COMMAND); unless ($OPT{'iconic'}) { # Realize a transient toplevel to display modo's initialization status. $STATUS = $MW->Toplevel; $STATUS->positionfrom('user'), $STATUS->geometry('+100+100'), $STATUS->title('Initializing modo'), $STATUS_B = $STATUS->Label(-bitmap => "@$LIBDIR/icon.xbm")->pack; $STATUS_L = $STATUS->Label( -text => 'Main Window ...', -width => 25, )->pack; $MW->idletasks; } update_status 'Global Stuff';
What’s that dangling anonymous subroutine doing there at the
top? Well, it simply defines what needs to be done when terminating
modo
. There are at least two ways to exit: either
by selecting Quit
or by having the window manager
close the main window, so it makes sense to define a subroutine. Thus,
$QUIT_COMMAND
is initialized with a code reference
that can be used whenever necessary.
As always, we first open the main window on some display—the
new
method accepts an optional parameter specifying
the particular display desired. (Be aware that modo
uses a special hash, named %OPT
, to hold argument
name-value pairs, whether created by default or extracted from the
command line.) Next there is a series of main window method calls
known as window manager commands, because they
are used to interact with the window manager.
We withdraw the main window to
unmap it from the display, so only the status
window will be visible (once it’s created). The
title
method draws text at the top of the
decorative border provided by the window manager, and the two “icon”
methods specify a name and X bitmap for the application’s icon.
minsize
restricts the user from resizing the window
smaller than fifty pixels in either dimension (there is also a related
maxsize
method). Finally, note the idiom for
registering a callback with the window manger to terminate an
application: simply associate a standard Perl/Tk callback with the
WM_DELETE_WINDOW
protocol.
Assuming the user didn’t fire up modo
iconified, we next create the top-level status widget. The methods
positionfrom
and geometry
are
suggestions to the window manager on where to place the new top-level.
Some window managers, fvwm
for instance, normally
require you to explicitly place top-level windows;
positionfrom(‘user’)
overrides this behavior.
Finally two label widgets are packed in the top-level, the first
containing modo
’s X bitmap and the second
containing the current initialization state. Since the X server tries
to buffer events to improve performance, idletasks
is used to flush idle callbacks and hence keep the display responsive.
(We’ll see more of event management in the next column.) A snapshot of
the status window is shown in Figure 36-2.
Lastly note the first call to subroutine
update_status
, which simply uses
configure
to change the text in the status window
via $STATUS_L
; there are numerous calls to this
subroutine sprinkled throughout modo
’s
initialization code. Doing this keeps users happy.
Another key aspect in user-friendly GUI design is providing a
reasonably consistent “look and feel.” Whether an application is
written for X, Windows, or the Mac, you find, and indeed
expect, a row of menubuttons (the menu bar) at
the top of the program’s main window. And the leftmost button is a
File
menubutton, which at least allows you to exit
the application. So to be conformant, modo
also has
a File
menubutton, which we’ll examine
now.
A menubutton is a subclass of a button,
meaning that it shares, or inherits, many of a button’s
characteristics. The big difference is that pushing a button executes
a callback whereas pushing a menubutton posts a menu. A
menu is simply a rectangular widget that contains
one or more menu items that when pressed, might
execute a callback, set a Perl variable, or invoke yet another menu
(an action called cascading). Pressing the
File
menubutton displays the menu shown in Figure 36-3.
The File
menu itself is composed of simple
button-like objects bound to callbacks. More precisely, we call these
command
menu items because they execute a command
(callback) when pressed. Other types of menu items include
cascade, checkbutton, radiobutton
, and
separator
.
The File
menu has three thin lines:
separator
menu items, whose sole purpose is to
visually separate logically distinct portions of the menu.
The File
menu also has a
tear-off, which is the dashed line above Abort.
Pressing a tear-off reparents the menu and puts
it under control of the window manager. Thus, it gets its own
decorative border, can be moved, iconifed, closed, and so on. By
default all Perl/Tk menus have a tear-off.
Here are some other facts you need to know about menus:
As a convenience, Perl/Tk automatically generates a menubutton’s associated menu widget when the first menu item is created. Two common cases where you need to manually create a menu are to disable the tear-off and to create menu cascades.
Menu items can be manipulated in several ways: added, configured, deleted, or invoked. To manipulate a menu item you refer to it either by its position in the menu (starting at zero) or by its text label. If there is a tear-off it is assigned index zero, making the first menu item have an index of one. Since a separator is a menu item, it too has a menu index. (I highly recommend referencing menu items by label name rather than index. You’ll know why as soon as you insert a new menu item in the middle of a menu and then have to hunt through your code changing index values!)
In case that was all as clear as mud, maybe some code will
clarify matters. Let’s create the application’s menubar using a frame,
$mb
, and pack our menubuttons in a row, from left
to right:
# File menu. my $mbf=$mb->Menubutton(-text => 'File', -underline => 0... ); $mbf->pack(qw(-side left)); $mbf->command( -label => 'Abort', -underline => 0, -command => &exit); $mbf->separator; my $close_command = [$MW => 'iconify']; $mbf->command( -label => 'Close', -underline => 0, -command => $close_command, -accelerator => 'Ctrl-w'), $MW->bind('<Control-Key-w>' => $close_command);
When Perl/Tk finishes building the Abort
menu
item we know that a menu widget has been generated with a tear-off
(index 0) and one command menu item (index 1, name Abort). (An often
asked question is: “How do I make a menu without a tear-off?” The
answer is you must explicitly create a menu with -tearoff
=> 0
, and then configure
the
menubutton with -menu => $your_menu
. Then you
can proceed normally.)
The Close
menu item (index 2) has an
associated keyboard accelerator. However, this just adds more text to
the menu item label; you still have to create the key binding. Since
the close
code is needed in two places, just create
a code reference and use that.
Another common menu item is the cascade
,
illustrated in Figure 36-4.
Pressing the Prefs
menubutton from the
menubar displays the leftmost menu, containing a cascade and command
menu item. Pressing the Odometers
cascade displays
the cascade’s menu, containing three radiobutton
menu items. (Of course, the cascade menu could contain another
cascade, which could have another cascade, which… well, you get the
picture.) Cascades are handled pretty much like menus without a tear-off, in that you create a menu
widget manually and then configure
the cascade to
point to it, like this:
# Prefs menu. my $mbp = $mb->Menubutton(-text => 'Prefs', ... ); $mbp->pack(qw(-side left)); my $odometers = 'Odometers'; $mbp->cascade(-label => $odometers, -underline => 0); $mbp->separator; $mbp->command(-label => 'Color Editor', -underline => 0, -state => $COLOR_STATE, ...);
So far, only -state
might be unfamiliar. Many
widgets have this option, which can have one of three possible values:
normal, active
, or disabled
.
Widgets start in the normal state, and when the cursor passes over
them they become active. If you place a widget in the disabled state,
it is dimmed and becomes unresponsive to button presses and other
bindings. We’ll see how $COLOR_STATE
is initialized
shortly.
my $mbpm = $mbp->cget(-menu); my $mbpmo = $mbpm->Menu; $mbp->entryconfigure($odometers, -menu => $mbpmo); $mbpmo->radiobutton(-label => 'Cursor', -variable => $OPT{'odometer'}, -value => 'cursor'), $mbpmo->radiobutton(-label => 'Pointer', -variable => $OPT{'odometer'}, -value => 'pointer'), $mbpmo->radiobutton(-label => 'Both', -variable => $OPT{'odometer'}, -value => 'both'),
Pay attention please: the Odometers
cascade
menu must be a child of the menu containing the
Odometer
cascade itself (here, the
Prefs
menu), hence the cget
call
to fetch the menu reference. Note that
entryconfigure
is to menus as configure
is to other
widgets, except you need to tell it which menu entry needs attention
(which is analogous to itemconfigure
for canvas
items). Notice also that the menu entry is referenced by name rather
than index.
Finally, three radiobutton menu items are added to the cascade menu. Just like ordinary radiobutton widgets, they allow you to select a single item from a list, and store its value in a variable. The actual value stored in the common variable depends on which radiobutton was pressed. (These widgets got their name because using them is similar to tuning an old fashioned car radio: selecting a station by pushing one button deselects all the other buttons by popping them out.)
If you’d like to see a complicated cascade created from a Perl
list-of-list-of-list data structure, take a gander at the
modo
source code responsible for generating the
Units
cascades; that’ll fry your eyes. The code is
available at http://www.oreilly.com/catalog/tpj2.
Let’s add some colors and incorporate the ColorEditor widget into our application. ColorEditor
lets you select a color attribute, say foreground
,
edit a color swatch and then apply the final color by calling a
special ColorEditor subroutine (the colorizer) that descends through
the application’s widgets and configures each in turn.
A ColorEditor widget is created in the standard manner:
$COLOR_STATE = $MW->depth > 1 ? 'normal' : 'disabled'; if ($COLOR_STATE eq 'normal') { $CREF = $MW->ColorEditor(-title => 'modo'), }
But there’s no need for one if your display can’t support it, so
first check the pixel depth of the display using the window
information command depth
. For monochrome displays
we don’t even bother creating the ColorEditor, and the menu item to
invoke it, which we just discussed, is dimmed.
Once the ColorEditor is created and initialized, you can use it
like a Dialog—just invoke its Show
method. The most
important thing to remember about the ColorEditor is that it maintains
a list of widgets to colorize:
every widget in the application present when the
ColorEditor was created. Sometimes this is good,
sometimes bad, and in modo
’s case it’s bad. Bad
because when $CREF
is created, some of the
applicable widgets aren’t there yet, and there are some that
are present that shouldn’t be colorized in the
first place. Of course, there are methods to deal with this, so as the
last step of initialization:
$CREF->configure(-widgets => [$MW, $MW->Descendants]); $CREF->delete_widgets( [$CREF, # ColorEditor... $CREF->Descendants, # and all its descendant widgets. $W_CODO->Descendants, # Odometer descendants because $W_PODO->Descendants, # the class handles configuration changes. ]);
The first line ensures that the main window,
$MW
, and all of its descendants in the widget
hierarchy are part of the color list. The second line then removes
particular widgets that should not be colorized. As a rule of thumb,
leave the ColorEditor alone in case you really mess things up, like
setting the foreground and background to the same color! And the two
composite odometers are excluded for the simple reason
that the foreground and background colors of digits to the right of
the “units” point are reversed, just like real odometers. How we deal
with this is somewhat subtle, as you’ll see in the next
section.
At last it’s time to discuss Perl/Tk composites featuring, of course, the odometer widget. The OO tables have turned and now you become a designer rather than a mere user! An odometer widget “ISA” frame. That is, it’s a subclass of a frame widget: odometer objects are simply “mega-widgets” composed of standard Tk widgets packed inside a frame (we’ll see what “ISA” is all about shortly). There are other kinds of extended widgets: a dialog widget ISA a top-level, while an axis widget is derived from, or “kind of,” a canvas. A common attribute of all these extended widgets is that they behave just like standard Tk widgets, basically because Nick took great pains to ensure they do.
Since an odometer is contained in a frame you can create an instance and pack it inside your application just like, say, a button. Let’s create a default odometer and add one millimeter to it:
$MW->Odometer->add(1, 1)->pack;
The result is shown in Figure 36-5.
Notice how methods such as add
and
pack
can be strung together, as long as they return
an object reference for the next method to operate upon. The odometer
is composed of six widgets: the odometer label, left and right labels
indicating total distance, a trip reset button, and left and right
labels indicating trip distance. Two labels are used for total and
trip distances so that foreground and background colors can be
reversed on either side of the “units” point. When
modo
creates its odometers it supplies some
arguments in the Perl/Tk “key => value” style, including
-odometerlabel
, a keyword unique to class
Odometer:
$W_CODO = $w->Odometer(-odometerlabel => 'Cursor', -font => $OPT{'fontname'}, -foreground => $OPT{'foreground'}, -background => $OPT{'background'}, -cursor => $CURSOR);
In order to see the primary features of a frame-like composite, I need to gut Odometer.pm, which later I’ll reconstruct piece by piece:
package Tk::Odometer; require 5.002; use Tk::Frame; use base qw/Tk::Frame/; Tk::Widget->Construct('Odometer'), sub Populate { my ($cw, $args) = @_; $cw->SUPER::Populate($args); # Create and pack frame subwidgets here. $cw->ConfigSpecs( ... ); return $cw; } # end Populate, Odometer constructor 1;
What we have is the definition of a new Perl/Tk widget class named “Tk::Odometer”, with six salient features:
A unique class (package) name.
A use Tk::Frame
that imports frame
definitions and methods.
Declaration of the @ISA
list, which is
how Perl implements method inheritance. For instance, when you
configure
an odometer Perl first looks for that
method in class Tk::Odometer. But as you’ll see, there is no such
method in this class, so Perl tries to locate that method by
looking at any classes in the @ISA
array. As it
turns out, Tk::Frame has no configure
method
either, but a frame has its own @ISA
list, so
the search continues up the class hierarchy. Rest assured that
Perl/Tk does indeed provide a configure
method
somewhere, but you don’t have to know just where—OO at its
best.
A call to Construct
that dynamically
creates the class constructor. Among other things, this magic
arranges a call to the class method Populate
when a new object is instantiated.
The actual code for Populate
, written by
the class implementor, which populates the incoming frame with the
requisite component widgets, specifies the widget’s configuration
options, and returns a composite reference.
Any number of class-specific methods (not shown here) to
manipulate objects of the new class. We already know of one for
odometers: add
.
Tk::Odometer::Populate
is called with two
arguments. $cw
is a reference to the partially
completed composite widget and $args
is a
reference to the argument hash (i.e., the
keyword/value pairs from the widget creation command). By convention,
$args
is immediately passed to
SUPER::Populate
, where, sometimes,
behind-the-scenes bookkeeping such as modifying configuration
specifications is performed.
Now, in standard Perl/Tk fashion, we create and arrange the
component widgets of the composite, using $cw
as
their parent (the list @PACK
holds common pack
attributes):
# Odometer label. my $l = $cw->Label->pack(@PACK); # Odometer total distance, left and right labels. $cw->make_odo('total')->pack(@PACK); # Odometer trip reset button. It's placed inside a container frame so # there is a background to color, since trying to configure the # composite containing frame results in nasty recursion problems. The # button is anchored southwest so it stays "attached to" the trip odometer. my $rbf = $cw->Frame(-relief => 'flat')->pack(@PACK); my $rb = $rbf->Button( -height => 2, -width => 5, -bitmap => 'gray50', -relief => 'flat', -command => [$cw => 'reset_trip'], -highlightthickness => 0)->pack(-anchor => 'sw', -expand => 1); # Odometer trip distance, left and right labels. $cw->make_odo('trip')->pack(@PACK); # Maintain instance variables in the composite widget hash. Instance # variables hold data particular to one instance of an Odometer object. # # reset = widget reference to trip reset button for bind # total_mm = total distance in millimeters # total_left = total distance left label for add # total_right = total distance right label for add # total_right_label = widget reference for colorizing # (ditto for trip_mm, trip_left, trip_right, and trip_right label.) $cw->{'reset'} = $rb; $cw->{'total_mm'} = 0; ($cw->{'total_left'}, $cw->{'total_right'}) = ($Z, $Z); $cw->reset_trip;
Once again there are several items worthy of note:
The -text
attribute of the odometer label
$l
is not specified. So, just when does this
happen and who does it? The answer follows shortly.
The -command
attribute of the reset
button $rb
invokes the class method
reset_trip
, emulating the reset button on a
real odometer.
make_odo
packs the left and right labels
side by side in a frame and creates the
-textvariable
references pointing to the above
instance variables.
I hinted at this, but one job Populate
should
not do, generally, is directly configure
its
components; instead it makes a call to ConfigSpecs
to specify configuration options and default values. Then, when
Populate
returns, Perl/Tk auto-configures the
composite, supplying ConfigSpec values or perhaps values
from the X options database:
# Now establish configuration specs so that the composite behaves like # a standard Perl/Tk widget. Each entry is a list of 4 items # describing the option: how to process a configure request, its # name in the resource database, its class name, and its default value. # # The Tk::Configure->new specification renames -odometerlabel to # -text, which is what Labels want, because -odometerlabel IS a Label. # # The DESCENDANTS specification applies configure recursively to all # descendant widgets. # # The METHOD specification invokes a method by the same name as the # option (without the dash), e.g.: # # $cw->background($bg); # # Normally you don't need configurators just for background and # foreground attributes, but an Odometer is special since the colors # are reversed for the right half of the odometers. # # The -cursor specification says to configure only the indicated list # of widgets (in this case there is but one, $rb, the trip reset button.) $cw->ConfigSpecs( -odometerlabel => [[Tk::Configure->new($l => '-text')], 'odometerlabel', 'odometerLabel', 'Odometer'], -font => ['DESCENDANTS', 'font', 'Font', 'fixed'], -background => ['METHOD', 'background', 'Background', '#d9d9d9'], -foreground => ['METHOD', 'foreground', 'Foreground', 'black'], -cursor => [[$rb], 'cursor', 'Cursor',['left_ptr']]); return $cw;
There’s still more work left, however. So far, we’ve created a
class constructor, but no methods to manipulate the objects it
creates. So let’s look at a few, starting with the simplest,
$W_CODO->get_total_distance
. Our program uses
this method to save its state information:
sub get_total_distance { shift->{'total_mm'} }
This method just returns the value from an odometer’s
total_mm
instance variable. The
shift
idiom is a shortcut for Perl’s builtin
shift
function, returning the odometer reference.
Here bind
is overridden by providing a version
specific to our class:
# Override bind to select trip reset button, the only sensible widget. # Build an argument list to bind so that the call behaves normally. sub bind { my ($odo, $event, $code) = @_; my @args = (); push @args, $event if defined $event; push @args, $code if defined $code; $odo->{'reset'}->bind(@args); return $odo; }
Finally, here’s add
, which displays the
millimeter count (modulus 100,000) in the user’s units. The only thing
new is the use of BackTrace
, the Perl/Tk way of
including traceback information:
sub add { my ($odo, $d, $u) = @_; $odo->BackTrace('Usage: $odo->add($distance, $units)') if @_ != 3; $odo->{'total_mm'} += $d; $odo->{'trip_mm' } += $d; my ($n1, $f1, $n2, $f2, $s); $n1 = $odo->{'total_mm'} * $u; $f1 = $n1 - int($n1); $n2 = $odo->{'trip_mm' } * $u; $f2 = $n2 - int($n2); $s = sprintf("%011.5f%011.5f", ($n1 % 100000) + $f1, ($n2 % 100000) + $f2); $odo->{'total_left'} = substr($s, 0, 5); $odo->{'total_right'} = substr($s, 6, 5); $odo->{'trip_left'} = substr($s, 11, 5); $odo->{'trip_right'} = substr($s, 17, 5); return $odo; }
The Odometer class has several private methods too. Unlike C++, in Perl a private method is only private because the class designer doesn’t document the interface. Be polite and only use documented public methods. Here, I need to show you three private methods to complete the ColorEditor discussion.
Now, Populate
used
ConfigSpecs
for foreground and background
configure
options, specifying the
METHOD
action, so when either of these odometer
attributes are configured, one of the following subroutines is called
with two parameters: an odometer widget reference and a color
value.
# Odometer background/foreground color subroutines. sub background { shift->bf(shift, '-foreground', '-background') } sub foreground { shift->bf(shift, '-background', '-foreground') }
These immediately call the following subroutine,
bf
. Remembering that an odometer’s component
widgets have been removed from ColorEditor’s color list, it’s up to
the class to colorize them. So bf
simply walks the
composite widget hierarchy, configuring each component in turn, but
swapping foreground for background (or vice-versa) upon encountering
any right-side label:
# Reverse background/foreground colors on right odometer labels. sub bf { my ($odo, $color, $bf1, $bf2) = @_; my $total_right = $odo->{'total_right_label'}; my $trip_right = $odo->{'trip_right_label'}; $odo->Walk( sub { my ($widget) = @_; if ($widget = = $total_right or $widget = = $trip_right) { $widget->configure($bf1 => $color); } else { $widget->configure($bf2 => $color); } }); }
So, we’re finished implementing, right? Wrong. Gee, all the code’s there, it’s tested and it works…what could be missing? How about user documentation! The Perl Way is to include a pod (Plain Old Documentation) in your class module. Check out Odometer.pm for an example.
In the next article, we’ll look at ways to handle events (like mouse clicks) in Perl/Tk.
3.145.166.167