Linux Format 30 [[ Typographical notes: indented text is program listing text surrounded in _underscores_ is italicised/emphasised ]] Perl tutorial TITLE: Getting graphical STRAP: Perl isn't just a text-mangling language. Charlie Stross shows how to use the popular Tk graphics library to add graphical front-ends to perl programs. SUBTITLE: Perl and GUIs These days, you almost certainly use a computer with a graphical user interface (GUI). The commonest -- but not the only GUI -- on Linux is the X11 Windowing System, although calling it a GUI is slightly misleading; it's a very raw toolkit, quite unlike the Macintosh or Windows GUIs in that it provides a low-level graphics library (rather than a full-blown user interface) and is designed to operate across TCP/IP networks (while the other GUIs are designed to work only a local machine). To use X11 as a GUI you need several components; a window manager (to provide borders around windows, facilitate menus, and so on), a desktop manager (to handle cut and paste intelligently, launch and manage programs, and provide some sort of way of getting at tools), and a file manager (to let you browse your files). KDE, GNOME, and XFCE all provide these tools. But what about writing graphical applications? GUI programming on UNIX used to be extraordinarily difficult. Because X11 is so low-level, higher level libraries were written that provided canned, ready-made widgets and components. (A widget is a graphical object, such as a button, window, scroll-bar, or icon. You can bind code to a widget, so that doing something to the widget makes the code run, and you can use "container" widgets (such as windows or menus) to group child widgets (such as scroll bars or menu entries) together. In C, it was typically necessary to use several different libraries in the process of writing just one program -- and it was hard work. In the early 1990's, Professor John Ousterhout was trying to write X11 applications using his Tcl embedded language. He became so frustrated with the inflexibility of the C approach that he built a graphical toolkit called Tk that provided a very high-level interface to X11 programming. Tk doesn't require the programmer to work with X11 input events, monitor the signals from the mouse and re-draw the pointer whenever it moves, or any of that stuff; instead, you can tell Tk to create a button object, tag it with descriptive information (such as a caption, and a piece of code to execute), then tell it where to appear on the screen. Tk takes care of the heavy lifting. It didn't take long for Tk to be ported to other languages, notably Python and Perl, and to appear in MacOS and Windows variants. A program written using Tk is therefore far easier to port to other platforms than a conventional X11 or Windows application. These days, Tk looks slightly dated -- it was designed to look similar to Motif, on UNIX and Linux -- but it's a fast, convenient, and easy tool for writing graphical gadgets, and the Tk module for Perl lets you write once and run on Windows or UNIX. (It should also be portable to MacOS X, and indeed a port of Tcl/Tk to MacOS X already exists.) (Tk isn't the only GUI programming toolkit available for Perl, but it's the oldest and by far the best documented. We'll look at some of the others in a future tutorial.) SUBTITLE: X11 programming fundamentals for Tk As far as X11 is concerned, the universe consists of windows. A window is a rectangle (or other geometric shape) which contains other windows. The first window, which contains all other windows, is called the _root_ window -- it's your screen backdrop. An application running under X11 may open other windows, but all of them are contained by the root window. Windows that contain other windows _manage_ their children. A child window is only visible while its coordinates are within the bounds of its parent, and the parent is allowed to do things to the child (such as send events to it). Windows -- and you -- communicate via events. Events are signals such as keystrokes, mouse button depressions and releases, and mouse movements; a constant stream of X11 events is received by the X server from your input devices (mouse and keyboard), and despatched to the appropriate program. X11 doesn't give you any screen furniture like scroll bars or moveable windows at all. These features are provided by a special program called a Window manager, whose sole job is to interpret the event stream coming from the X11 server and retransmit events to where they're needed -- and to provide window decorations and a consistent look and feel for every visible window. Graphical applications other than window managers (which aren't written in Perl/Tk) typically start by creating a main window. This may or may not be transparent; in any event, all the other widgets created by the application live inside it, and receive their controlling events through it. The windows exist in a hierarchy, like a tree, and inherit their attributes from entities closer to the roots of the tree. A widget, such as a button, is actually a whole bunch of windows that work in synchrony. For example, a button usually consists of six windows -- a main enclosing window, four smaller windows that act as bevels, and an inner window. The inner window may have some text rendered on it. Code needs to be tied to the button so that when a mouse-down event is received by the innermost window it (and the bevel windows) changes colour to reflect the fact that it's in the "down" position, and when a mouse-up is received some other code may be triggered (to do whatever action the button is meant to do). Each of these windows has coordinates associated with each corner, and has to respond correctly when a parent window is dragged (i.e. when the parent's X and Y coordinates change, it has to update each child window in the button so that they appear to move together). Note that there's a whole lot more to X11 than this quick overview suggests. If you really want to know what's going on but can't be bothered reading the gigantic X11 Programming Manuals (published in dead-tree form by O'Reilly and Associates, www.ora.com), I'd strongly recommend "The Joy of X: An Overview of the X Window System" by Nial Mansfield, pub. Addison-Wesley, ISBN 0-201-56512-9); it provides a solid overview of how X works, without going down to the functional call and API level. This is about perfect for a Tk or Qt programmer (although you'd need to do more background reading before trying to program in raw X11 or a low-level library such as Motif). SUBTITLE: The Tk approach Tk takes the heavy lifting out of this job by providing an object called "button". A button object is a wrapper for all the code that puts a button on the screen. You can send messages to the button object telling it what colour it is, how large it is, what text it carries, and what code to execute when it's pressed. (The Tk button is a bit smarter than this sounds: you can tell it what type of border decoration to apply to itself, and it'll reveal itself as a flat 2D, raised 3D, grooved, or other button without you having to worry about anything other than its essential button-ness.) Actually, Tk provides a lot more than just a button: there's a full set of basic widgets, including windows, menu bars, several different types of button, a canvas (upon which you can draw in various line styles and with various polygons and images), text entry boxes, and the whole panoply of standard GUI elements. (In addition, there are extensions to Tk that provide "mega-widgets" -- for example, a notebook widget -- which consist of a collection of smaller elements configured to work together as a unit, and with their own OO interface.) Widgets live inside other widgets, just as windows in X11 live inside other windows. Right at the top of the hierarchy in Tk is a thing called a TopLevel widget; this is almost invariably a subclass of a fundamental thingy called a window. (Can you see where this is going?) You start writing a Tk application by creating the main, top level widget. This acts as a container for scroll bars, windows, buttons, and whatever screen furniture your application needs. These are laid out on screen using a thing called a _Geometry_ _Manager_ -- actually a set of rules that indicate where widgets go in relation to one another. Finally, you hang subroutines called _callbacks_ off each widget that the user is likely to click on. It's the callbacks that do the back-end heavy lifting associated with your program; for example, if there's a "Quit" button on your application, and it has files open, the button should have a callback that closes the files in good order then exits the program. So far we've avoided looking at a huge issue -- how does the application deal with input? Simple Perl programs typically read text from the standard input and write something to standard output. The nature of the input is usually tightly constrained, and it all enters the program at the same place -- the line of code that reads STDIN. But input to a GUI program can come from a whole range of sources; the keyboard, the mouse, the window manager sending "resize" events, and so on. And if a user can click the mouse on the "Quit" button, or the "File" menu, or a drawing canvas, how do you handle the possible options? GUI programs have to respond to input rapidly, from a variety of sources. To cope with this, they are structured around an _event loop_. An event loop is a case construct. At the top, events (from the X server) come in. These are matched against windows or widgets that are _registered_ with the program, until the one that's supposed to handle the event is found. Control is handed over to the widget, then returned to the event loop to process the next event. (Some multi-threaded GUI systems spawn new threads to handle events, just to maintain the responsiveness of the event loop; if events are processed slowly, the user is the first person to know about it, so the event loop tends to be tightly coded.) You may have noticed that widgets are only checked for events if they're _registered_. They're also only visible if they're registered. To register a widget so that the main event loop can draw it on screen and hand events to it, in Tk you tell the widget that it belongs to a geometry manager: this acts as the interface between the main event loop and the widget. Unmanaged widgets aren't visible and can hang around, dormant, until a piece of code is called that registers them; then they appear (or disappear if we unregister them). This allows us to do things like open up new windows, dialogues, and so on. SUBTITLE: Hello, world! Here's a "hello.pl" program for you to scratch your head over: ///BEGIN CODE/// #!/usr/local/bin/perl use Tk; my ($m) = new MainWindow; my ($label) = $m->Label( -text => "Hello, world!" ); my ($button) = $m->Button( -text => "Quit", -command => sub {exit} ); $label->pack(); $button->pack(); MainLoop; ///END CODE/// When you run this -- assuming you've got Perl/Tk installed -- you will see a tiny window like this: ///INCLUDE hello.png/// Let's go through it line-by-line and see what it's doing: First, we call "use Tk" to load the Perl/Tk interface. Having done that, we start by creating a MainWindow object. A MainWindow is exactly what it sounds like -- the top level object in a Tk application, which acts as a container for everything else. Here, we call it $m (which is a reference -- pointer -- to the object). Next, we create an object called a Label, and save a reference to it as $label. A label is exactly that -- a textual tag that goes into a window. To create that label inside our MainWindow, we call the Label method on $m. We're also passing it a parameter, "-text", with a value of "Hello, World!" -- this is the text associated with the label object. Now we create another object; a Button. A Button is a widget which, when pressed and released, executes some code. We pass the parameter "-text" to tell our button what its caption is, and the "-command" parameter is a reference to the code to execute when it's released. In this case, we use an anonymous subroutine, but we could define a subroutine elsewhere by name and pass a reference to it: "-command => \&mysub". (Remember, we're basically assembling a tree of widgets by hand. They're glued together with references -- Perl pointers -- and they all hang off the top level widget, $m.) At this point, we've defined our MainWindow and two child widgets, but we haven't registered them with the event loop that despatches events to them. Nor have we said how large they are, or where to position them in the MainWindow. We achieve both these goals by calling pack() on each widget. pack() is one of the Tk geometry managers; we'll look at it in a moment. For now, what it does is add the widget to the event loop (so that it can respond to events) and figure out where to draw it on the screen. Finally: all the preceding stuff is basically setup -- declaring widgets, saying what they do when you activate them, registering them, and so on. The Tk program is not yet actually displaying anything or responding to events! To set everything in motion, you need to call MainLoop -- a subroutine that runs the event loop continuously until either the program exits or you call destroy() on the MainWindow object. (destroy() is provided by Tk, and it causes the event loop to exit and all the widgets to disappear; you can use it if you want to do some non-interactive, non-graphical tidying up while exiting your application.) Now, what happens if you click on the "Quit" button? Quite simply, the event is passed to the $button object, which executes the callback (subroutine) pointed to by the "-command" parameter. In this case, the subroutine is a wrapper for "exit", which causes Perl to exit (as usual). SUBTITLE: Geometry managers Where do your widgets appear on the screen when you register them? Control over positioning is important because without being able to do this, you can't control the layout of widgets in your application's user interface. But you can't make assumptions about the display device; somebody might be using a 640x480 monitor with an 8-bit colour map, while somebody else might have a 1600x1400 display with 32-bit colour. Tk originally provided three geometry managers -- ways of managing the positioning of widgets. These are the packer, the placer, and the grid. You specify that a given geometry manager has control of a widget by calling it as a method on the widget: ///BEGIN CODE/// my $cb = $m->CheckButton(-text => "red"); $cb->pack() ///END CODE/// This creates a checkbutton widget with the text "red", and tells it that it's controlled by the packer (without supplying any options to tell the packer how to behave). Each geometry manager displays widgets managed by it in a different way. The packer assigns invisible bounding boxes to a widget; these are anchored (either to other widgets under packer control, or to the toplevel window), and can be resized, and the widgets show up within the non-overlapping boundaries defined by the boxes. The grid, in contrast, defines a row/column table and lets you place widgets in specific positions in the grid. (This is great for spreadsheet-like layouts.) Unlike the other two managers, which do not permit widgets to overlap and which define widget positions relative to some other entity (allocation boxes in the case of the packer, the table layout in the case of the grid), the placer allows you to place widgets at absolute coordinates within the parent window. It allows buttons and other widgets to overlap, and enables all sorts of confusion -- but it's the most appropriate manager to use for generating graphics on the fly, rather than simple user interfaces that rely on widgets at fixed positions. Here's a simple test program that creates a bunch of buttons, and that demonstrates how we use the packer: ///BEGIN CODE/// #!/usr/local/bin/perl use Tk; # create a TopLevel widget to hold everything else my $m = new MainWindow; # now create some checkboxes, and a "Quit" button my $label = $m->Label(-text => "Main Window")->pack; my $b1 = $m->Checkbutton( -text => "This is great!", -command => sub { print STDERR "This is great!\n"; } ); my $b2 = $m->Checkbutton( -text => "I'm bored", -command => sub { print STDERR "I'm bored\n"; } ); my $b3 = $m->Checkbutton( -text => "This is stupid!", -command => sub { print STDERR "This is stupid!\n"; } ); my $exit = $m->Button(-text => "Quit", -command => sub {exit} ); # now use the packer to position them all $b1->pack( -side => "left", -expand => 1 ); $b2->pack( -side => "left", -expand => 1 ); $b3->pack( -side => "left", -expand => 1 ); $exit->pack( -side => "bottom", -expand => 1, -fill => "x", -before => $b1 ); # and now, we run! MainLoop; exit; ///END CODE/// This produces a window like this: ///INCLUDE hello2.png HERE/// There are several points to note about this program before we examine how it uses the packer. First, we can append the pack() commands to each widget when we create the thing -- the reason we don't do so in this example is to make it easier to read. Each of the Checkbuttons $b1 to $b3 has a simple callback that makes it print some text on the standard output when we check it; this is a handy debugging technique you can use. The Checkbuttons are not grouped together and they don't keep track of any underlying state -- but you could use the -command callbacks to make them update an underlying data structure and, if necessary, spawn a new widget that would show up as soon as it is managed by one of the geometry managers. The pack() commands are the ones we want to focus on here. When you manage a widget with pack(), it allocates space in the toplevel widget from the remaining area which is not already occupied by widgets. By calling pack() with the "-side => left" argument, we are telling it that the current widget is to be packed to the left of the available space. Subsequent widgets are packed into the window in whatever space is left, and the "-expand => 1" parameter tells the packer to allow the widgets to expand to fill the available space. The $exit widget is treated differently. First, we tell the packer to anchor it to the bottom of the window. The toplevel window expands to the minimum size necessary to accomodate all the managed widgets in the order in which they are packed. Because the first three items are packed to the left, in a row, the $exit widget ought to be positioned against the bottom of the window -- and to the right of the row of checkbuttons. But we also specified "-before => $b1". This tells the packer to put $exit first in its list of widgets to allocate space for in the window. And we gave the "-fill => x" argument; this tells the packer to allow the button to expand to fill the X-axis of its allocation rectangle. If we delete the "-fill => 1" line, we get this: ///INCLUDE hello3.png HERE /// And if we delete the "before => $b1" line, we see this: ///INCLUDE hello4.png HERE/// When you take an empty window and tell the packer to allocate space for a widget at the left, it creates an invisible allocation rectangle that occupies the entire vertical height of the window, at the left hand side of the window. If you tell it to pack against the top, the allocation rectangle is as wide as the window and occupies the top of it. Here's a small program (from "Learning Perl/Tk") that demonstrates where buttons end up if we pack them to left, right, top and bottom: ///BEGIN CODE/// #!/usr/local/bin/perl use Tk; # create a TopLevel widget to hold everything else my $m = new MainWindow; # now create some buttons (hit any button to exit) my $label = $m->Label(-text => "Main Window")->pack; my $b1 = $m->Button( -text => "Top", -command => sub { exit; } ); my $b2 = $m->Button( -text => "Left", -command => sub { exit; } ); my $b3 = $m->Button( -text => "Right", -command => sub { exit; } ); my $b4 = $m->Button( -text => "Bottom", -command => sub { exit; } ); # now use the packer to position them all $b1->pack( -side => "top", ); $b4->pack( -side => "bottom", ); $b2->pack( -side => "left", ); $b3->pack( -side => "right", ); # and now, we run! MainLoop; exit; ///END CODE/// This produces something like this: ///INCLUDE pack1.png HERE/// It's pretty ugly because the allocation rectangles aren't filled. If we tell Tk to allow widgets to expand to fill their allocation rectangles (albeit without overlapping), like this: ///BEGIN CODE/// $b1->pack( -side => "top", -fill => "both" ); ///END CODE/// We get a much prettier result: ///INCLUDE pack2.png HERE/// Note that the reason we get the big buttons at top and bottom is that we packed them -- added them to the packer's list of managed widgets -- before we packed the left and right widgets. Try experimenting by changing the order in which the widgets are packed (just cutting and pasting the pack() commands into a different order). You may find the results a little surprising! The packer doesn't just position widgets in a window relative to one another; it can be used to destroy widgets so that they don't show up any more: ///BEGIN CODE/// $b3->packForget() ///END CODE/// This causes widget $b3 to vanish from the packing order and thence the window. The packer can return information about a widget using packInfo: ///BEGIN CODE/// @info = $b3->packInfo(); print "[", join("][", @info), "]\n"; ///END CODE/// Which returns something like this: ///BEGIN CODE/// [-in][MainWindow=HASH(0x100f0bb8)][-anchor][center][-expand][0][-fill][both][-ipadx][0][-ipady][0][-padx][0][-pady][0][-side][right] ///END CODE/// The information here includes a reference to the parent widget, as well as all the applied attributes of the widget we're querying. We can also use packSlaves to retreive a list of references to all the child widgets managed by a parent that's using the packer: ///BEGIN CODE/// my @info = $m->packSlaves(); print "[", join("]\n[", @info), "]\n"; ///END CODE/// Which produces something like this: ///BEGIN CODE/// [Tk::Label=HASH(0x102a91b0)] [Tk::Button=HASH(0x102c36f0)] [Tk::Button=HASH(0x102c38c4)] [Tk::Button=HASH(0x102c36a8)] [Tk::Button=HASH(0x102c384c)] ///END CODE/// Using these returned references we can allow a parent to tweak its child widgets attributes -- for example, by using the -ipadx and -ipady methods to re-pack them in a larger bounding box. SUBTITLE: Going Further This tutorial barely scratches the surface of what you can do with Perl/Tk. In addition to the buttons we've been playing with, and the packer we've used to position them in the window, Tk provides a wide range of basic widgets -- different types of buttons with text or bitmaps, rendered in different ways, checkbuttons and radio buttons, labels, text entry widgets, scrollbars (which can be linked to widgets to allow its contents to be panned across), list boxes, text widgets, the canvas (a 2 dimensional drawing widget), menus and menubuttons, and frames. We'll use (or look at) some of these in the next tutorial. One topic we haven't even looked at is how to go about designing a graphical interface that does something useful. The examples we've seen so far don't do that: they aren't wired up to a callback that does anything. Here's a final example which acts as a front-end to the wc(1) word count program When you run it, you get two buttons: a "pick a file" button and a "quit" button: ///INCLUDE wc1.png HERE/// "Pick a file" leads you to select a file: ///INCLUDE wc2.png HERE/// After picking a file, the "pick" button vanishes and is replaced by the number of characters, words, and lines in the file: ///INCLUDE wc3.png HERE/// This program shows a number of useful features -- using the geometry() method to tell the toplevel window where to position itself, using Tk::FileSelect (an external mega-widget that provides a file selection box) to grab a file, and destroying a widget and replacing it with another on the fly using packForget(). Most importantly, it illustrates a principle of graphical programming -- the callback does the heavy lifting, while the graphical user interface is almost entirely independent of the code that actually does stuff. ///BEGIN CODE/// #!/usr/local/bin/perl # graphical front end to wc use Tk; use Tk::FileSelect; $rootdir = shift @ARGV || `pwd`; chomp $rootdir; $fname = ""; $m = new MainWindow; # create some widgets $label = $m->Label(-text => "Word Count"); $pickbutton = $m->Button(-text => "Pick a file", -command => sub{$label = &getfile($m, $label)} ); $quitbutton = $m->Button(-text => "Quit", -command => sub {exit} ); # let's tell the mainWindow to show up at the top left $m->geometry("+0+0"); # now let's pack our widgets $label->pack(-side => "top", -fill => "both" ); $pickbutton->pack(-side => "top", -fill => "both" ); $quitbutton->pack(-side => "top", -fill => "both" ); MainLoop; exit; # now we design a callback that does something useful -- calls # Tk::FileSelect to get a filename, then runs it through wc(1), and # prints the results in our window sub getfile { my ($m) = shift @_; # the toplevel widget my ($label) = shift @_ if @_; # the label widget if ($label) { $label->DESTROY; } my ($fref) = $m->FileSelect(-directory => $rootdir); my ($filename) = $fref->Show; my $wc = join("\n", `wc $filename`); chomp $wc; my $label = $m->Label(-text => "wc stats: $wc"); $label->pack( -fill => "both", -expand => 1, -side => "top", -before => $pickbutton ); $pickbutton->packForget(); return $label; }; ///END CODE/// (END)