Recently in User Interfaces Category

Still More Perl Lightning Articles

It has been common practice within the Perl community for ages to ship distributions with a Makefile.PL so that the user will be able to install the packages when he retrieves them, either via the shell which the CPAN/CPANPLUS modules offer or via manual CPAN download.

The Makefile.PL consists of meta-information, which in the case of the distribution HTML::Tagset is:

 # This -*-perl-*- program writes the Makefile for installing this distribution.
 #
 # See "perldoc perlmodinstall" or "perldoc ExtUtils::MakeMaker" for
 # info on how to control how the installation goes.

 require 5.004;
 use strict;
 use ExtUtils::MakeMaker;

 WriteMakefile(
     NAME            => 'HTML::Tagset',
     AUTHOR          => 'Andy Lester <andy@petdance.com>',
     VERSION_FROM    => 'Tagset.pm', # finds $VERSION
     ABSTRACT_FROM   => 'Tagset.pm', # retrieve abstract from module
     PMLIBDIRS       => [qw(lib/)],
     dist            => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', },
     clean           => { FILES => 'HTML-Tagset-*' },
 );

Of interest are the arguments to WriteMakefile(), because they influence the Makefile written by ExtUtils::MakeMaker after the user has invoked the usual build and install procedure:

 % perl Makefile.PL
 % make
 % make test
 # make install

Module::Build, Successor of ExtUtils::MakeMaker?

As Ken Williams grew tired of ExtUtils::MakeMaker and its portability issues, he invented Module::Build, a successor of ExtUtils::MakeMaker. One goal of Module::Build is to run smoothly on most operating systems, because it takes advantage of creating Perl-valid syntax files only and does not rely upon crufty Makefiles, which are often subject to misinterpretation, because so many incompatible flavors of make exist in the wild.

The current maintainer of ExtUtils::MakeMaker, Michael G. Schwern, elaborated about this problem in his talk reachable via "MakeMaker is DOOMED."

Module::Build Distribution "Skeleton"

If you take in consideration the distribution HTML::Tagset again, the rough skeleton suitable for Module::Build having converted the Makefile.PL by Module::Build::Convert into a Build.PL, the output would be:

 # This -*-perl-*- program writes the Makefile for installing this distribution.
 #
 # See "perldoc perlmodinstall" or "perldoc ExtUtils::MakeMaker" for
 # info on how to control how the installation goes.
 # Note: this file has been initially generated by Module::Build::Convert 0.24_01

 require 5.004;
 use strict;
 use warnings;

 use Module::Build;

 my $build = Module::Build->new
   (
    module_name => 'HTML::Tagset',
    dist_author => 'Andy Lester <andy@petdance.com>',
    dist_version_from => 'Tagset.pm',
    add_to_cleanup => [
                        'HTML-Tagset-*'
                      ],
    license => 'unknown',
    create_readme => 1,
    create_makefile_pl => 'traditional',
   );
  
 $build->create_build_script;

As you can see, while ExtUtils::MakeMaker prefers uppercased arguments, Module::Build goes by entirely lowercased arguments, which obey the rule of least surprise by being as intuitive as a description can be.

The build and installation procedure for a Module::Build distribution is:

 % perl Build.PL
 % perl Build
 % perl Build test
 # perl Build install

Module::Build::Convert's State of Operation

Module::Build::Convert actually does all of the background work and can be safely considered the back end, whereas make2build is the practical front-end utility. Module::Build::Convert currently exposes two kinds of operation: static approach and dynamic execution. The static approach parses the arguments contained within the Makefile.PL's WriteMakefile() call, whereas dynamic execution runs the Makefile.PL and captures the arguments provided to WriteMakefile().

Module::Build::Convert parses statically by default, because the dynamic execution has the downside that code will be interpreted and the interpreted output will be written to the Build.PL, so you have to conclude that the user of the distribution will end up with predefined values computed on the author's system. This is something to avoid, whenever possible! If the parsing approach fails, perhaps looping endlessly on input, Module::Build::Convert will reinitialize to perform dynamic execution of the Makefile.PL instead.

Data Section

Module::Build::Convert comes with a rather huge data section containing the argument conversion table, default arguments, sorting order, and begin and end code. If you wish to change this data, consider making a ~/.make2buildrc file by launching make2build with the -rc switch. Do not edit the Data section within Module::Build::Convert directly, unless you are sure you want to submit a patch.

Argument Conversion

On the left-hand side is the MakeMaker's argument name, and on the right-hand side the Module::Build's equivalent.

 NAME                  module_name
 DISTNAME              dist_name
 ABSTRACT              dist_abstract
 AUTHOR                dist_author
 VERSION               dist_version
 VERSION_FROM          dist_version_from
 PREREQ_PM             requires
 PL_FILES              PL_files
 PM                    pm_files
 MAN1PODS              pod_files
 XS                    xs_files
 INC                   include_dirs
 INSTALLDIRS           installdirs
 DESTDIR               destdir
 CCFLAGS               extra_compiler_flags
 EXTRA_META            meta_add
 SIGN                  sign
 LICENSE               license
 clean.FILES           @add_to_cleanup

Default Arguments

These are default Module::Build arguments to added. Arguments with a leading # are ignored.

 #build_requires       HASH
 #recommends           HASH
 #conflicts            HASH
 license               unknown
 create_readme         1
 create_makefile_pl    traditional

Sorting Order

This is the sorting order for Module::Build arguments.

 module_name
 dist_name
 dist_abstract
 dist_author
 dist_version
 dist_version_from
 requires
 build_requires
 recommends
 conflicts
 PL_files
 pm_files
 pod_files
 xs_files
 include_dirs
 installdirs
 destdir
 add_to_cleanup
 extra_compiler_flags
 meta_add
 sign
 license
 create_readme
 create_makefile_pl

Begin Code

Code that precedes converted Module::Build arguments. $(UPPERCASE) are stubs being substituted by Module::Build code.

 use strict;
 use warnings;

 use Module::Build;

 $MAKECODE

 my $b = Module::Build->new
 $INDENT(

End Code

Code that follows converted Module::Build arguments. $(UPPERCASE) are stubs being substituted by Module::Build code.

 $INDENT);

 $b->create_build_script;

 $MAKECODE

make2build Basic Usage

Using make2build is as easy as launching it in the directory of the distribution of which Makefile.PL you wish to convert.

For example:

% make2build

You may also provide the full path to the distribution, assuming, for example, you didn't cd directly into the distribution directory.

% make2build /path/to/HTML-Tagset*

In both cases, the command will convert any found Makefile.PL files and will generate no output because make2build acts quiet by default.

make2build Switches

As make2build aims to be a proper script, it of course, provides both the -h (help screen) and -V (version) switches.

 % make2build -h
 % make2build -V

In case you end up with a mangled Build.PL written, you can examine the parsing process by launching make2build with the -d switch, enabling the pseudo-interactive debugging mode.

 % make2build -d

Should you not like the indentation length or judge it to be too small, increase it via the -l switch followed by an integer.

 % make2build -l length

If you don't agree with the sorting order predefined in Module::Build::Convert, you may enforce the native sorting order, which strives to arrange standard arguments with those seen available in the Makefile.PL.

 % make2build -n

The argument conversion table, default arguments to add, the sorting order of the arguments, and the begin and end code aren't absolute, either. Change them by invoking make2build with the -rc switch to create a resource configuration file in the home directory of the current user; that is likely ~/.make2build.rc.

 % make2build -rc

While make2build is quiet by default, there are two verbosity levels. To enforce verbosity level 1, launch make2build with -v. To enforce verbosity level 2, use -vv.

With -v, the code will warn about Makefile.PL options it does not understand or skips. With -vv, it will accumulate -v output and the entire generated Build.PL.

 % make2build -v
 % make2build -vv

You may execute the Makefile.PL in first place, but such usage is deprecated because Module::Build::Convert downgrades automatically when needed.

 % make2build -x (deprecated)

Swinging with Perl

Phil Crow

Perl does not have a native graphical user interface (GUI) toolkit. So we use all manner of existing GUI tools in front of our Perl applications. Often we use a web browser. We have long had Perl/Tk and other libraries based on C/C++. Now we can also use Java's Swing toolkit with similar ease.

In my sample application, when the user presses a button, Perl evaluates an arithmetic expression from the input text box. The result appears in another text box. I'll show the code for this application a piece at a time with a discussion after each piece. To see the whole thing, look in the examples directory of the Java::Swing distribution.

    #!/usr/bin/perl
    use strict; use warnings;

    BEGIN {
        $ENV{CLASSPATH} .= ':/path/to/Java/Swing/java'
    }

Java::Swing needs certain Java classes to be in the class path before it loads, so I've appended a path to those classes in a BEGIN block (this block must come before using Java::Swing).

    use Java::Swing;

This innocuous statement magically sets up namespaces for each Java Swing component, among other things.

    my $expression  = JTextField->new();
    my $answer      = JTextField->new( { columns => 10 } );
    my $submit      = JButton   ->new("Evaluate");
    my $frame       = JFrame    ->new();
    my $root_pane   = $frame->getContentPane();
    my $south_panel = JPanel->new();

After using Java::Swing, you can refer to Swing components as Perl classes. You can even pass named parameters to their constructors, as shown for the second JTextField.

    $south_panel->add(JLabel->new("Answer:"), "West");
    $south_panel->add($answer,                "Center");
    $south_panel->add($submit,                "East");

    $root_pane->add($expression,  "North");
    $root_pane->add($south_panel, "South");

    $frame->setSize(300, 100);
    $frame->show();

Most work with the components is the same as in any Java program. If you don't understand the above code, consult a good book on Swing (like the one from O'Reilly).

    my $swinger = Java::Swing->new();

This creates a Java::Swing instance to connect event listeners and to control the event loop.

    $swinger->connect(
        "ActionListener", $submit, { actionPerformed => \&evaluate }
    );

    $swinger->connect(
        "WindowListener", $frame, { windowClosing => \&ending }
    );

Connection is simple. Pass the listener type, the object to listen to, and a hash of code references to call back as events arrive.

    $swinger->start();

Start the event loop. After this, the program passively waits for event callbacks. It stops when one of the callbacks stops the event loop.

    sub evaluate {
        my $sender_name = shift;
        my $event       = shift;

        $answer->setText(eval $expression->getText());
    }

My evaluation is simple. I retrieve the text from the expression JTextField, eval it, and pass the result to setText on the answer JTextField. Using eval raises possible security concerns, so use it wisely.

    sub ending {
        $swinger->stop();
    }

When the user closes the window, I stop the event loop by calling stop on the Java::Swing instance gained earlier. This kills the program.

With Java::Swing, you can build Swing apps in Perl with some important bits of syntactic sugar. First, you don't need to have separate Java files or inline sections. Second, you can pass named arguments to constructors. Finally, you can easily connect event listeners to Perl callback code.

Scriptify Your Module

Josh McAdams

Recently during an MJD talk at Chicago.pm, I saw a little Perl trick that was so amazingly simple and yet so useful that it was hard to believe that more mongers in the crowd hadn't heard of it. The trick involved taking your module and adding a driver routine to it so the module could run as a script.

To illustrate, start with an example module that contains two utility subroutines that convert weights between pounds and kilograms. The subroutines accept some number and multiplies it by a conversion factor.

  package WeightConverter;
  
  use strict;
  use warnings;
  use constant LB_PER_KG => 2.20462262;
  use constant KG_PER_LB => 1/LB_PER_KG;
  
  sub kilograms_to_pounds { $_[0] * LB_PER_KG; }
  
  sub pounds_to_kilograms { $_[0] * KG_PER_LB; }

Assuming that the real module has a little error checking and POD, this module would serve you just fine. However, what if you decided that we needed to be able to easily do weight conversions from the command line? One option would be to write a Perl script that used WeightConverter. If that seems like too much effort, there is a one-liner that would do conversions.

  perl -MWeightConverter -e 'print WeightConverter::kilograms_to_pounds(1),"\n"'

This would do the trick, but it is a lot to remember and isn't very fun to type. There is a lot of benefit available from saving some form of script, and believe it or not, the module can hold that script. All that you have to do is write some driver subroutine and then call that subroutine if the module is not being used by another script. Here is an example driver for WeightConverter.

This example driver script just loops through the command-line arguments and tries to find instances where the argument contains either a k or p equal to some value. Based on whether or not you are starting with pounds or kilograms, it calls the appropriate subroutine and prints the results.

  sub run {
    for (@ARGV) {
      if(/^[-]{0,2}(k|p)\w*=(.+)$/) {
        $1 eq 'k' ?
          print "$2 kilograms is ", kilograms_to_pounds($2), " pounds\n" :
          print "$2 pounds is ", pounds_to_kilograms($2), " kilograms\n" ;
      }
    }
  }

Now all that is left is to tell the module to run the run subroutine if someone has run the module on its own. This is as easy as adding one line somewhere in the main body of the module.

  run unless caller;

All this statement does is execute the run subroutine unless the caller function returns a value. caller will only return true if WeightConverter is being used in another script. Now, this module is usable in other scripts as well as on the command line.

  $> perl WeightConverter.pm -kilos=2 -pounds=145 -k=.345
  2 kilograms is 4.40924524 pounds
  145 pounds is 65.7708937051548 kilograms
  .345 kilograms is 0.7605948039 pounds

Mocks in Your Test Fixtures

by chromatic

Since writing Test::MockObject, I've used it in nearly every complex test file I've written. It makes my life much easier to be able to control only what I need for the current group of tests.

I wish I'd written Test::MockObject::Extends earlier than I did; that module allows you to decorate an existing object with a mockable wrapper. It works just as the wrapped object does, but if you add any mocked methods, it will work like a regular mock object.

This is very useful when you don't want to go through all of the overhead of setting up your own mock object but do want to override one or two methods. (It's almost always the right thing to do instead of using Test::MockObject..)

Another very useful test module is Test::Class. It takes more work to understand and to use than Test::More, but it pays back that investment by allowing you to group, reuse, and organize tests in the same way you would group, reuse, and organize objects in your code. Instead of writing your tests procedurally, from the start to the end of a test file, you organize them into classes.

This is most useful when you've organized your code along similar lines. If you have a base class with a lot of behavior and a handful of subclasses that add and override a little bit of behavior, write a Test::Class-based test for the base class and smaller tests that inherit from the base test for the subclasses.

Goodbye, duplicate code.

Fixtures

Test::Class encourages you to group related tests into test methods. This allows you to override and extend those groups of tests in test subclasses. (Good OO design principles apply here; tests are still just code, after all.) One of the benefits of grouping tests in this way is that you can use test fixtures.

A test fixture is another method that runs before every test method. You can use them to set up the test environment--creating a new object to test, resetting test data, and generally making sure that tests don't interfere with each other.

A standard test fixture might resemble:

  sub make_fixture :Test( setup )
  {
      my $self        = shift;
      $self->{object} = $self->test_class()->new();
  }

Assuming that there's a test_class() method that returns the name of the class being tested, this fixture creates a new instance before every test method and stores it as the object attribute. The test methods can then fetch this as normal.

Putting Them Together

I recently built some tests for a large system using Test::Class. Some of the tests had mockable features--they dealt with file or database errors, for example. I found myself creating a lot of little Test::MockObject::Extends instances within most of the tests.

Then inspiration struck. Duplication is bad. Repetition is bad. Factor it out into one place.

The insight was quick and sudden. If Test::MockObject::Extends is transparent (and if it isn't, please file a bug--I'll fix it), I can use it in the test fixture all the time and then be able to mock whenever I want without doing any setup. I changed my fixture to:

  sub make_fixture :Test( setup )
  {
      my $self        = shift;
          my $object      = $self->test_class()->new();
      $self->{object} = Test::MockObject::Extends->new( $object );
  }

The rest of my code remained unchanged, except that now I could delete several identical lines from several test methods.

Do note that, for this to work, you must adhere to good OO design principles in the code being tested. Don't assume that ref is always what you think it should be (and use the isa() method instead).

Sure, this is a one-line trick, but it removed a lot of busy work from my life and it illustrates two interesting techniques for managing tests. If you need simpler, more precise mocks, use Test::MockObject::Extends. If you need better organization and less duplication in your test files, use Test::Class. Like all good test modules, they work together almost flawlessly.

Test-Driving X11 GUIs

Driving X11 GUIs using X11::GUITest

Introduction

Interfaces to GUI applications like DCOP or D-BUS allow you to interact with GUI applications in order to get at their internal states or set some arbitrary states.

Sometimes GUIs don't allow for such interaction and you need to "click" them. If you're writing such an application, you need some sort of regression tests for it to make sure your widget/windows are as accessible as they should be. If this is the case, there is a Perl module to help you: X11::GUITest.

Be aware that X11::GUITest allows you to drive a GUI, but you can't "read" data written in a widget, such as a button or an edit box. More on this in the Limitations section below.

To install X11::GUITest, run:

$ perl -MCPAN -e 'shell'
install X11::GUITest
quit

A Simple Example

I've included two example programs. One is tested.pl and it serves as an example GUI. The other is tester.pl that starts and drives the tested program.

You need Tk installed for the tested GUI. Tk comes as a package in most GNU/Linux distributions or other *NIX OSes. Download both files in the same folder, run ./tester.pl, and watch.

What are they doing and how do they work?

Starting a GUI

First thing to do prior to driving a GUI is to start the driven program. While you can use fork and exec or any other means, X11::GUITest comes with a routine of its own.

Use StartApp( $tested_application ); to start a GUI, which results in starting the desired application in an asynchronous manner.

If you want to start an application and wait for it to finish before going on, use RunApp.

Finding a Window

After having the GUI started, you need to search for it among the other open windows on your desktop. For this, use FindWindowLike(), WaitWindowLike(), or WaitWindowViewable(), depending on what you need. Their names are pretty much self-explanatory.

Usually you need to have only one instance of the tested application started:

@windows = FindWindowLike( $tested_app_title );
print "* Number of $tested_app_title windows found: ", scalar @windows, "\n";

if ( @windows == 1 ) {
     print "* Only one instance found, going on ...\n";
} else {
    print "* The number of $tested_app_title instances is different than 1\n";
    print "exiting ...\n";
    exit;
}

FindWindowLike() returns a list of windows that match the search criteria, which is a regular expression to match against the window title. In case there is more than one window that matches the criteria, either you have the same window started multiple times, or the regular expression isn't specific enough.

Sending Keyboard Events to an Application

Having found the window, (when you know that there is only one, you can access it as the first element of @windows, namely $windows[0]), you probably want to send it some keystrokes. Use SendKeys() to do this.

If you are having a busy X server, or just want your testing to be easy for the human eye to watch, set the delay between the keystrokes (in milliseconds) with SetKeySendDelay():

SetKeySendDelay( $delay );

To send Alt+O, followed by a delay of $delay milliseconds, then e:

SendKeys( '%(o)e' );

Besides sending plain text to an application, like sending the infamous "Hello World" to an editor window, you may have noticed that the previous example sent a combination of keys. Do so by using modifiers. The modifier keys are:

  • ^, Ctrl
  • %, Alt
  • +, Shift

The X11::GUITest documentation has a complete list of special keys and their "encodings."

You may also find it useful to use QuoteStringForSendKeys() in the case of complicated strings.

Sending Mouse Events to an Application

Sending keys may be not enough in some situations. Having an application that has keyboard shortcuts is nice, but not all of them support it. Sometimes you may need to send mouse events.

To get the absolute position of the appropriate window on your desktop:

my ($x, $y, $width, $height) = GetWindowPos($edit_windows[0]);

Suppose that you want to click right in the middle of it. First, compute the position of the middle of the window:

$x += $width  / 2;
$y += $height / 2;

Now move the mouse:

MoveMouseAbs( $x, $y );

Then press the right mouse button:

PressMouseButton M_RIGHT;

Do something useful, and then release the mouse button. (Don't forget to do that when you're using PressMouseButton; otherwise, you may experience "strange" desktop behavior when your testing application exits.)

ReleaseMouseButton M_RIGHT

You could replace PressMouseButton() and ReleaseMouseButton() with ClickMouseButton() if you don't have anything to do between pressing and releasing the mouse button.

In the example programs, there's something to do--navigating the context menu with keystrokes.

Moving a Window

This is a neat and interesting feature: the ability to move windows. While it is useful to impress your friends with having their favorite mail program moving up and down, its utility lies in the fact that you can arrange the tested windows on the desktop so they are all visible.

MoveWindow( $window_id, $x, $y );

Limitations

As you may have noticed reading the example code, there is almost no way of validating the fact that you are indeed interacting with the right widget or window. The functions you can use for this are FindWindow* or WaitWindow*, which return a list of windows whose titles match an arbitrary regexp, and the functions that deal with child windows, such as IsChild() and GetChildWindows().

While you may pass the window ID to a testing program, using external means to validate the tested application (such as indicating the coordinates on the screen), the problem is that you can't grab a widget's contents.

Also, while you might be tempted to parse the child tree of an application to get from the main window to one of its children, this doesn't work every time. Plenty of GUIs spawn other windows at the top level, and the spawned windows have as root window the topmost window (which is the desktop).

Here's an example of the problem that uses Mozilla Firefox. Before running the test, you must meet some prerequisites:

  • Back up your preferences before running the tests.
  • Go to Edit -> Preferences -> General -> Connection Settings and set it to "Direct connection to the Internet."
  • Click OK, and then OK, and close the browser.

Now run the Firefox.pl example code.

The test program assumes that when the Preferences window pops up, the General menu is selected.

Open Mozilla Firefox again, go to Preferences, select the Web Features menu, click OK, and exit the browser.

Rerun the Firefox.pl program, and watch it.

It has no idea which menu is selected, because every menu component belongs to the same window, having the same title.

Writing GUIs for Testability

Having in mind the strengths and weaknesses of X11::GUITest, it's critical to design graphical user interfaces that are easy to test. This way, you shorten your maintenance time, as you can have a tester program that can help check that the GUI hasn't lost some of its windows in the development/maintenance process.

Of course, when displaying a license text when your GUI starts, you don't have the means to check that the contents are unchanged using X11::GUITest.

What you can do is to ensure that all child windows are "in place" and that a user can access them in the same way as he/she could in previous versions.

If you define ways of navigating the GUI using keyboard shortcuts so that you can reach any "leaf" window starting from the top-level window, then it's trivial for a test program to navigate the same way you do and ensure that all windows are reachable as they were in previous versions.

Consider the following code based on the tested Tk program:

$menu{'OTHER'} = $menu_bar->cascade(
    -label   => 'Other',
    -tearoff => 0,
);

$menu{'OTHER'}->command(
    -label   => 'Editor',
    -command => sub {
        edit_window();
    }
);

It defines a piece of menu from the overall menu of the application. As you may notice, there are no keyboard shortcuts that you can use to access the Editor window.

Thinking of testability, you could go to some lengths to test this piece of code to ensure that the Editor window is reachable and that it indeed pops up. You could record the application's position on the screen and then click the Other button, then move the mouse over the Editor button and click it. I'm sure you can spot some caveats here, among them:

  • You need to make sure that the application is always on the screen at some known coordinates (use GetWindowPos()) or maybe that the test always moves the window to the same place (use MoveWindow()).
  • You have to take into consideration font size changes, localization, and resolution changes so that you are sure you are clicking in the right place.

This kind of testing is fragile and error-prone. You can make things simpler and more robust: add keyboard shortcuts for each action. You gain two main benefits: you make some users (like me) happier and ease the testing process. You just need to define all the "paths" that you need to "walk" and define the child window titles so you know you've reached them.

Here's a slight adjustment to the tested application so that it provides keyboard shortcuts:

$menu{'OTHER'} = $menu_bar->cascade(
    -label   => '~Other',
    -tearoff => 0,
);

$menu{'OTHER'}->command(
    -label   => '~Editor',
    -command => sub {
        edit_window();
    }
);

sub edit_window {
    # some initialization code here ...

    $edit_window = $main_window->Toplevel();

    # Set the title of the Editor window
    $edit_window->title("This is an edit window");

    # the rest of the code here ....

}

This piece of code is easier to test. Navigate the application until you reach the Editor window:

SendKeys('%(o)e');

Now you should have the Editor window spawned. Grab a list of windows having the title matching the Editor window's title:

@edit_windows = FindWindowLike( $edit_title );

Check to see whether the Editor window is present. Also, there should be only one Editor window started:

if ( @edit_windows == 1 ) {
    # code here
} else {
    # we have zero or more than one Editor window, so something is not quite
    # right
}

This kind of code is easy to extend, as you can store the application window hierarchy in some external file outside of the program source (in some sort of markup language file, or anything that suits your needs). Having this external definition of the windows' hierarchy and their properties, the tester program can read the same file the tested application uses; thus, both know the same keyboard shortcuts and window titles.

Program logic errors and/or bugs in underlying libraries used are easier to catch before you release the software.

Conclusion

As you can see, there is no easy way to test an entire GUI application with X11::GUITest, but you can test the important parts. Also, for some actions you can use a mixed approach, such as initiating an event using the application interface (connecting to a remote server protected with a user/password auth scheme) and picking the results from a log file.

While the testing done in the previous paragraph is necessary, it is not sufficient. It would be great if there were someone willing to pick up the module and research whether it could be possible for X11::GUITest to be able to fetch data from the widgets, making it possible to "read" the contents of a window (from a text widget, for example).

This kind of testing is more complete than simply driving the GUI.

Of course, you could also use X11::GUITest to write a "record and playback" application. You might only need GetMousePos(), IsMouseButtonPressed(), and the other mouse functions. As I said earlier, in my opinion this kind of testing is too fragile.

The problem is that you can't validate the contents of the windows.

Making Menus with wxPerl


In a previous article about wxPerl published on Perl.com, Jouke Visser taught the very basics of wxPerl programming. In this article, I will continue with Jouke's work, explaining how to add menus in our wxPerl applications. I will cover the creation, editing, and erasure of menus with the Wx::Menu and Wx::MenuBar modules, and also will show examples of their use.

Conventions

I assume that you understand the wxPerl approach to GUI programming, so I won't explain it here. The following code is the base for the examples in this article:

use strict;
use Wx;

package WxPerlComExample;

use base qw(Wx::App);

sub OnInit {
    my $self  = shift;
    my $frame = WxPerlComExampleFrame->new(undef, -1, "WxPerl Example");

    $frame->Show(1);
    $self->SetTopWindow($frame);

    return 1;
}

package WxPerlComExampleFrame;

use base qw(Wx::Frame);

use Wx qw( 
    wxDefaultPosition wxDefaultSize wxDefaultPosition wxDefaultSize wxID_EXIT
);

use Wx::Event qw(EVT_MENU);

our @id = (0 .. 100); # IDs array

sub new {
    my $class = shift;
    my $self  = $class->SUPER::new( @_ );

    ### CODE GOES HERE ###

    return $self;
}

### PUT SUBROUTINES HERE ###

package main;

my($app) = WxPerlComExample->new();

$app->MainLoop();

@id is an array of integer numbers to use as unique identifier numbers. In addition, the following definitions are important:

  • Menu bar: The bar located at the top of the window where menus will appear. This is a particular instance of Wx::MenuBar.
  • Menu: A particular instance of Wx::Menu.
  • Item: An option inside of a (sub)menu.

A Quick Example

Instead of wading through several pages of explanation before the first example, here is a short example that serves as a summary of this article. Note that I have divided it in two parts. Add this code to the base code in the WxPerlComExampleFrame constructor:

# Create menus
my $firstmenu = Wx::Menu->new();
$firstmenu->Append($id[0], "Normal Item");
$firstmenu->AppendCheckItem($id[1], "Check Item");
$firstmenu->AppendSeparator();
$firstmenu->AppendRadioItem($id[2], "Radio Item");

my $secmenu   = Wx::Menu->new();
$secmenu->Append(wxID_EXIT, "Exit\tCtrl+X");

# Create menu bar
my $menubar   = Wx::MenuBar->new();
$menubar->Append($firstmenu, "First Menu");
$menubar->Append($secmenu, "Exit Menu");

# Attach menubar to the window
$self->SetMenuBar($menubar);
$self->SetAutoLayout(1);

# Handle events only for Exit and Normal item
EVT_MENU( $self, $id[0], \&ShowDialog );
EVT_MENU( $self, wxID_EXIT, sub {$_[0]->Close(1)} );

Insert the following code into the base code at the line ### PUT SUBROUTINES HERE ###.

use Wx qw(wxOK wxCENTRE);

# The following subroutine will be called when you click in the normal item

sub ShowDialog {
  my($self, $event) = @_;
  Wx::MessageBox( "This is a dialog", 
                  "Wx::MessageBox example", 
                   wxOK|wxCENTRE, 
                   $self
               );
}

Run this example to see something like Figures 1 and 2.

a menu with complex sub-items
Figure 1. A menu with complex sub-items

a menu with a single sub-item
Figure 2. A menu with a single sub-item

Programming Menus

To add a menu to your wxPerl application, you must know how to use two Perl modules that come with WxPerl: Wx::MenuBar and Wx::Menu. Wx::MenuBar creates and manages the bar that contains menus created with Wx::Menu. There is also a third module involved: Wx::MenuItem. This module, as its name implies, creates and manages menu items. You usually don't need to use it, because almost all of the operations you need for a menu item are available through Wx::Menu methods.

Using Wx::Menu

Creating a menu with Wx::Menu is as easy as:

my $menu = Wx::Menu->new();

Now $menu is a Wx::Menu object. WxPerl has five types of items. The first is the normal item, upon which you can click to get a response (a dialog or something else). The second is the check item, which has the Boolean property of being checked or not (independent of another check items). The third item is the radio item, which is an "exclusive check item;" if you check a particular radio item, other radio items in its radio group get unchecked instantly. The fourth type of item is the separator, which is just a straight line that acts as a barrier that separates groups of similar items inside of a menu. The fifth type is the submenu, an item that expands another menu when the mouse cursor is over it.

Setting Up Menu Items

To create a normal item for your menu, write:

$menu->Append($id, $label, $helpstr);

where $id is an unique integer that identifies this item, $label is the text to display on the menu, and $helpstr is a string to display in the status bar. (This last argument is optional.) Note that every menu item must have an unique identifier number in order to be able to operate with this item during the rest of the program. (From now on, $id will denote the unique identifier number of a menu item.)

To create a check or radio item, the methods are analogous to Append--AppendCheckItem and AppendRadioItem, respectively. Add a separator with the AppendSeparator method; it does not expect arguments. Create a submenu with the AppendSubMenu method:

$menu->AppendSubMenu($id, $label, $submenu, $helpstr);

where $submenu is an instance to another Wx::Menu object. (Don't try to make that a submenu be a submenu of itself, because the Universe will crash or, in the best case, your program won't execute at all.)

While append methods add menu items in the last position of your menus, Wx::Menu gives you methods to add menu items at any position you want. For instance, to add a normal item at some position in a menu:

$menu->Insert($pos, $id, $label, $helpstr);

where $pos is the position of the item, starting at 0. To add a radio item, check item, or separator, use the InsertRadioItem, InsertCheckItem, or InsertSeparator methods. As usual, the latter takes no arguments. To insert a submenu, use the InsertSubMenu method:

$menu->InsertSubMenu($pos, $id, $label, $submenu, $helpstr);

You can also insert an item at the first position by using the Prepend method:

$menu->Prepend($id, $label, $helpstr);

PrependRadioItem, PrependCheckItem, and PrependSeparator methods are also available. As you might expect, there's a PrependSubMenu method that works like this:

$menu->PrependSubMenu($id, $label, $submenu, $helpstr);

Sometimes, a menu grows to include too many menu items, and then it's impractical to show them all. For this problem, Wx::Menu has the Break method. When called, it causes Wx to place subsequently appended items into another column. Call this method like so:

$menu->Break();
Menu Item Methods

Once you have created your items, you need some way to operate on them, such as finding information about them through their identifier numbers, getting or setting their labels or help strings, enabling or disabling them, checking or unchecking them, or removing them. For example, you may want to retrieve some specific menu item in some point of your program. To do this, use the FindItem method in either of two ways:

my $menuitem_with_the_given_id = $menu->FindItem($id);
my ($menuitem, $submenu)        = $menu->FindItem($id);

where $menuitem is the corresponding Wx::MenuItem object with the identifier $id, and $submenu is the (sub)menu to which $menuitem belongs. You can also retrieve a menu item through the FindItemByPosition method (but remember that positions start at 0):

my $menuitem = $menu->FindItemByPosition($pos);

Wx::Menu provides methods to get or set properties of menu items. To set a property, there are two methods: SetLabel and SetHelpString. A SetLabel call might be:

$menu->SetLabel($id, $newlabel);

SetHelpString works similarly:

$menu->SetHelpString($id, $newhelpstr);

To retrieve the label or help string of a particular item, use the GetLabel and GetHelpString methods. Both methods expect the menu item identifier number as the sole argument.

Every menu item has an enabled property that makes an item available or unavailable. By default, all items are enabled. To enable or disable a particular menu item, use the Enable method:

$menu->Enable($id, $boolean);

where $boolean is 0 or 1, depending if you want to disable or enable it, respectively. Maybe your next question is how to check if a menu item is enabled; use the IsEnabled method:

$menu->IsEnabled($id);

This returns TRUE or FALSE, depending on the status of the menu item.

Radio items and check items have the checked property that indicates the selection status of the item. By default, no check item is checked at the start of the execution of your program. For radio items, the first one created is checked at the start of execution. Use the Check method to check or uncheck a radio or check item:

$menu->Check($id, $boolean);

To determine if a menu item is checked, use IsChecked:

$menu->IsChecked($id);

This method, as does IsEnabled, returns TRUE or FALSE.

It's also possible to get the number of menu items your menu has. For this, use the GetMenuItemCount method:

$menu->GetMenuItemCount();

note that if @args is the argument's array, then $menu->Append(@args) and $menu->Insert($menu->GetMenuItemCount(), @args) are the same.

Finally, it's important to know that there are three ways to remove an item from a menu (honoring Larry Wall's phrase: "There's more than one way to do it"). The first is the Delete method, which just kills the menu item without compassion:

$menu->Delete($id);

This method returns nothing. Be careful--WxWidgets documentation says that the Delete method doesn't delete a menu item that's a submenu. Instead, the documentation recommends that you use the Destroy method to delete a submenu. In wxPerl, this isn't true. Delete is certainly capable of deleting a submenu, and is here equivalent to the Destroy method. I don't know the reason for this strange behavior.

The Destroy method looks like this:

$menu->Destroy($id);

If you want to remove an item but not destroy it, then the Remove method is for you. It allows you to store the menu item that you want to delete in a variable for later use, and at the same time delete it from its original menu. Use it like so:

my $removed_item = $menu->Remove($id);

Now you have your menu item with the identifier $id in the $removed_item variable (it now contains a Wx::MenuItem object). You can now use this variable to relocate the removed item into another menu with the append methods. For example:

$other_menu->Append($removed_item);

does the same thing as:

$other_menu->Append($id_removed_item, $title_removed_item, 
    $helpstr_removed_item);

but in a shorter way.

Finally, it's useful to be able to remove a submenu's menu item. You can't use the Destroy, Delete, or Remove methods, because they don't work. Instead, you need to do something like this:

my ($mitem, $submenu) = $menu->FindItem($mitem_id);

where $mitem_id is the identifier number of the submenu's menu item you're looking for. $submenu is a Wx::Menu object, just as $menu is, and hence you can use all the methods mentioned here, so the only thing you have to do to remove $mitem from $submenu is:

$submenu->Delete($mitem_id);

As the good reader that I am sure you are, you already have realized that this isn't the only thing you can do with the $submenu object. In fact, you can now add new menu items to your submenu, delete another menu item, and in general do everything mentioned already.

Using Wx::MenuBar

You have created your menus and obviously want to use them. The last step to get the job done is to create the menu bar that will handle your menus. When you want to create a menu bar, the first step is to enable your code to handle menu events. This is the job of the Wx::Event module:

use Wx::Event qw(EVT_MENU)

Now create a Wx::MenuBar object:

my $menubar = Wx::MenuBar->new();

This object will contain all of the menus that you want to show on your window. To associate a menu bar with a frame, call the SetMenuBar method from Wx::Frame:

$self->SetMenuBar($menubar);

where $self is the Wx::Frame object inherited in WxPerlComExampleFrame's constructor. Note that if your application has MDI characteristics, or has many windows, then you have to take in account that Wx first sends menu events to the focused window. (I won't cover this issue in this article, so for more information, review the WxWidgets documentation.) Finally, be sure to call the EVT_MENU subroutine as many times as you have menu items that execute some action when clicked:

EVT_MENU($self, $menu_item_id, \&subroutine);

where $self is the object of your package's new method, $menu_item_id is the unique identifier of the menu item involved, and subroutine is the name of the subroutine that will handle the click event you want to catch.

Setting Up Menus

The first thing to do once you have created your menu bar is to attach your menus to the menu bar. There are two methods for this: Append and Insert. Append, as you might expect, attaches a menu in the last position:

$menubar->Append($menu, $label);

where $menu is the menu created in the previous section and $label is the name to display for this menu in the menu bar. To insert a menu in an arbitrary position, use the Insert method:

$menubar->Insert($pos, $menu, $label);

where $pos is the position of your menu, starting at 0.

Menu Methods

Wx::MenuBar provides some methods that are also present in Wx::Menu and work in the same way. This methods are Check, Enable, FindItem, GetLabel, GetHelpString, SetLabel, SetHelpString, IsChecked, and IsEnabled. Besides these methods, Wx::MenuBar has its own set of methods to manage the properties of the menu bar. For example, as a menu item, a menu has its own enabled property, which you toggle with the EnableTop method:

$menubar->EnableTop($pos, $boolean);

where $pos is the position of your menu (starting at 0) and $boolean is TRUE or FALSE, depending on whether you want that menu enabled. Note that you can use this method only after you attach your menu bar to the window through the SetMenuBar method.

Wx::MenuBar has methods to retrieve an entire menu or menu item given its title or (menu title, menu item label) pair, respectively. In the first case, use the code:

$menu_with_the_given_title = $menubar->FindMenu($title);

In the second case:

$menu_item = $menubar->FindMenuItem($menu_title, $menu_item_label);

In both cases, the returned variables are Wx::Menu objects. You can also retrieve a menu if you provide its position (starting at 0):

$menu_with_the_given_pos = $menubar->GetMenu($pos);

As in the Wx::Menu case, Wx::MenuBar provides methods to set or get the label of a specific menu and to retrieve the number of menus in a menu bar. Those methods are SetLabelTop, GetLabelTop, and GetMenuCount respectively. Use them like this:

$menu->SetLabelTop($pos, $label);
my $menu_label = $menu->GetLabelTop($pos);
my $num_menu   = $menu->GetMenuCount();

where $pos is the position of the menu and $label is the new label that you want to put on your menu. Note that GetLabelTop's result doesn't include accelerator characters inside the returned string.

Finally, Wx::MenuBar gives two more choices to remove a menu. The first method is Replace, which replaces it with another menu:

$menubar->Replace($pos, $new_menu, $label);

where $pos is the position of the menu to remove, $new_menu is the new menu that will be in the $pos position, and $label is the label to display on the menu bar for $new_menu. The second choice is to remove a menu, just by removing it with the Remove method:

my $removed_menu = $menubar->Remove($pos);

Remove returns the $removed_menu object, so if you need it in the future, it'll be still there waiting for you.

Example

With all of that explained, I can show a full, working example. As before, add this code to the base code in the blank spot in the WxPerlComExampleFrame constructor.

# Create menus
# Action's sub menu
my $submenu = Wx::Menu->new();
$submenu->Append($id[2], "New normal item");
$submenu->Append($id[3], "Delete normal item");
$submenu->AppendSeparator();
$submenu->Append($id[4], "New check item");
$submenu->Append($id[5], "Delete check item");
$submenu->AppendSeparator();
$submenu->Append($id[6], "New radio item");
$submenu->Append($id[7], "Delete radio item");

# Disable items for this submenu
for(2..7) {
    $submenu->Enable($id[$_], 0);
}

# Actions menu
my $actionmenu = Wx::Menu->new();
$actionmenu->Append($id[0], "Create Menu"); # Create new menu
$actionmenu->Append($id[1], "Delete Menu"); # Delete New Menu
$actionmenu->AppendSeparator();
$actionmenu->AppendSubMenu($id[100], "New Item", $submenu); # Create item submenu
$actionmenu->AppendSeparator();
$actionmenu->Append(wxID_EXIT, "Exit\tCtrl+X"); # Exit

# At first, disable the Delete Menu option
$actionmenu->Enable($id[1], 0);

# Create menu bar
$self->{MENU} = Wx::MenuBar->new();
$self->{MENU}->Append($actionmenu, "Actions");

# Attach menubar to the window
$self->SetMenuBar($self->{MENU});
$self->SetAutoLayout(1);

# Handle events
EVT_MENU($self, $id[0], \&MakeActionMenu);
EVT_MENU($self, $id[1], \&MakeActionMenu);
EVT_MENU($self, $id[2], \&MakeActionNormal);
EVT_MENU($self, $id[3], \&MakeActionNormal);
EVT_MENU($self, $id[4], \&MakeActionCheck);
EVT_MENU($self, $id[5], \&MakeActionCheck);
EVT_MENU($self, $id[6], \&MakeActionRadio);
EVT_MENU($self, $id[7], \&MakeActionRadio);

EVT_MENU($self, wxID_EXIT, sub {$_[0]->Close(1)});

This code creates a menu called Actions with the following options inside:

  • Create Menu: When a user clicks this option, the program creates a new menu called New Menu at the right side of the Actions menu. The Create Menu option is enabled by default, but creating the menu disables this option.
  • Delete Menu: Deletes the menu created with Create Menu. This option is disabled by default and is enabled when New Menu exists.
  • New normal item: This option creates the Normal item option on New Menu when it exists. It is disabled by default.
  • Delete normal item: Deletes Normal item when it exists. It is disabled by default.
  • New check item: Creates the Check item option on New Menu when it exists. It is disabled by default. Check item is unchecked by default.
  • Delete check item: Deletes Check item when it exists. It is disabled by default.
  • New radio item: Creates the Radio item option on New Menu when it exists. It is disabled by default. Radio item is checked by default.
  • Delete radio item: Deletes Radio item when it exists. It is disabled by default.
  • Exit: Exits the program.

Once the code has created the menu, it attaches the menu to the menu bar saved on $self->{MENU}, then calls the EVT_MENU subroutine eight times to handle all of the menu events from Action's menu items. Add the following code to the base code where it says ### PUT SUBROUTINES HERE ###:

# Subroutine that handles menu creation/erasure
sub MakeActionMenu {
    my($self, $event) = @_;

    # Get Actions menu
    my $actionmenu    = $self->{MENU}->GetMenu(0);

    # Now check if we have to create or delete the New Menu
    if ($self->{MENU}->GetMenuCount() == 1) {
        # New Menu doesn't exist

        # Create menu
        my $newmenu = Wx::Menu->new();
        $self->{MENU}->Append($newmenu, "New Menu");       

        # Disable and Enable options
        $actionmenu->Enable($id[0], 0); # New menu
        $actionmenu->Enable($id[1], 1); # Delete menu
        $actionmenu->Enable($id[2], 1); # New normal item
        $actionmenu->Enable($id[3], 0); # Delete normal item
        $actionmenu->Enable($id[4], 1); # New check item
        $actionmenu->Enable($id[5], 0); # Delete check item
        $actionmenu->Enable($id[6], 1); # New radio item
        $actionmenu->Enable($id[7], 0); # Delete radio item
    } else {
        # New Menu exists

        # Remove menu
       $self->{MENU}->Remove(1);

        # Enable and disable options
        $actionmenu->Enable($id[0], 1);

        for(1..7) {
               $actionmenu->Enable($id[$_], 0);
        }
    }

    return 1;
}

# Subroutine that handles normal item creation/erasure
sub MakeActionNormal {
    my($self, $event) = @_;
    # Check if New Menu exists
    if($self->{MENU}->GetMenuCount() == 2) {
        # New menu exists

        # Get Action menu
        my $actionmenu = $self->{MENU}->GetMenu(0);
        my $newmenu    = $self->{MENU}->GetMenu(1);

        # Check if we have to create or delete a menu item
        if($actionmenu->IsEnabled($id[2])) {
            # Create normal menu item
            $newmenu->Append($id[50], "Normal item");           

            # Disable and Enable options
            $actionmenu->Enable($id[2], 0);
            $actionmenu->Enable($id[3], 1);
        } else {
            # Delete menu item
               $newmenu->Delete($id[50]);

            # Enable and disable options
            $actionmenu->Enable($id[2], 1);
            $actionmenu->Enable($id[3], 0);
        }
    }

    return 1;
}

# Subroutine that handles check item creation/erasure
sub MakeActionCheck {
    my($self, $event) = @_;

    # Check if New Menu exists
    if($self->{MENU}->GetMenuCount() == 2) {
        # New menu exists

        # Get Action menu
        my $actionmenu = $self->{MENU}->GetMenu(0);
        my $newmenu    = $self->{MENU}->GetMenu(1);

        # Check if we have to create or delete a menu item
        if($actionmenu->IsEnabled($id[4])) {
            # Create check item
               $newmenu->AppendCheckItem($id[51], "Check item");

           # Disable and Enable options
           $actionmenu->Enable($id[4], 0);
           $actionmenu->Enable($id[5], 1);
        } else {
           # Delete menu item
           $newmenu->Delete($id[51]);

              # Enable and disable options
              $actionmenu->Enable($id[4], 1);
              $actionmenu->Enable($id[5], 0);
        }
    }

    return 1;
}

# Subroutine that handles radio item creation/erasure

sub MakeActionRadio {
    my($self, $event) = @_;

    # Check if New Menu exists
    if($self->{MENU}->GetMenuCount() == 2) {
        # New menu exists

        # Get Action menu
        my $actionmenu = $self->{MENU}->GetMenu(0);
        my $newmenu    = $self->{MENU}->GetMenu(1);

        # Check if we have to create or delete a menu item
        if ($actionmenu->IsEnabled($id[6])) {
               # Create radio item
              $newmenu->AppendRadioItem($id[52], "Radio item");

              # Disable and Enable options
              $actionmenu->Enable($id[6], 0);
              $actionmenu->Enable($id[7], 1);
        } else {
              # Delete menu item
              $newmenu->Delete($id[52]);

              # Enable and disable options
              $actionmenu->Enable($id[6], 1);
              $actionmenu->Enable($id[7], 0);
        }
    }

    return 1;
}

The MakeActionMenu subroutine handles events for the New Menu and Delete Menu items. It first gets the Actions menu and checks whether the New Menu exists by retrieving the number of menus attached to the $self->{MENU} menu bar. If the new menu doesn't exist, the number of menus in the menu bar is equal to 1, and the subroutine then creates New Menu. If it exists, the subroutine deletes New Menu.

The MakeActionNormal, MakeActionCheck, and MakeActionRadio subroutines are almost identical. They differ only in the involved identifier numbers. These subroutines handle events for New normal item, Delete normal item, New check item, Delete check item, New radio item, and Delete radio item, respectively. They first check if New Menu exists (the number of menus attached to the menu bar is equal to 2). If so, they check if the options to create normal, check, or radio items are enabled, respectively. If the corresponding option is enabled, then the corresponding item doesn't exist on New Menu, and the subroutine creates it. If the option to create an item is disabled, then that item exists on New Menu and hence it must be deleted. If New Menu doesn't exist, the subroutines do nothing. Figure 3 shows how there are no options available if New Menu does not exist, and Figure 4 shows New Menu with two options added.

no available options without New Menu
Figure 3. No available options without New Menu

New Menu has menu options
Figure 4. New Menu has menu options

Conclusion

As this article has shown, menu programming with wxPerl is an extremely simple task. Wx::MenuBar and Wx::Menu's methods are very easy to use and remember. If you understood this article, you can do anything possible with menus in your wxPerl programs.

I have covered almost all of the available methods in Wx::Menu and Wx::MenuBar. I left out some methods related to pop-up menus, but I hope to cover these topics in future articles. WxPerl is a really great module, but its lack of adoption is due to its severe lack of documentation. This situation must be reversed, and this article is a small contribution to that cause.

See Also

Visit the home of the Perl programming language: Perl.org

Sponsored by

Powered by Movable Type 5.02