Chapter 36. The Mouse Odometer

Steve Lidie

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, the mouse odometer
Figure 36-1. modo, the mouse odometer

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.

Measuring Distance

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.

modo’s initialization window
Figure 36-2. modo’s initialization window

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
Figure 36-3. The File menu

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.

Cascading preferences for modo
Figure 36-4. Cascading preferences for modo

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.

The ColorEditor Widget

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.

Composite Widgets

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.

A Tk::Odometer object
Figure 36-5. A Tk::Odometer object

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:

  1. A unique class (package) name.

  2. A use Tk::Frame that imports frame definitions and methods.

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

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

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

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

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.166.167