Channels ▼
RSS

Web Development

Programming Graphical Applications with Gtk2-Perl, Part 1


November, 2003: Programming Graphical Applications with Gtk2-Perl, Part 1

Gavin works for the domain registry CentralNic, and was a coauthor of Professional Perl Programming (Wrox Press, 2001). He can be contacted at gavin.brown@uk.com.


Gtk+ is a toolkit for creating graphical applications on X11 systems. It has also been ported to Windows, BeOS, and runs on Mac OS X using Apple's X11 server. It was originally created for the GIMP, a popular image-manipulation program, and later became the toolkit for GNOME (GNU Network Object Model Environment).

Perl already has mature, stable, and popular bindings for the 1.x series of Gtk+ and for the GNOME libraries. However, bindings for the 2.x series of Gtk+ reached the 1.0 version in October, so now is a good time to talk about the things that you can do with the new libraries.

This article will be comprised of two parts: This month, I'll discuss some basics of event-based programming and introduce you to widgets and containers in Gtk+. Next month, I'll discuss manipulating graphics, introduce the Rapid Application Development (RAD) tool Glade, and show you how to create your own widgets.

What's New in Gtk+ 2

Gtk+ 2.x builds on the old 1.x series with a number of improvements. These include support for font antialiasing using Xft, better font management using Fontconfig, integrated and themeable stock icons, and an advanced Model-View-Controller (MVC) model for list and tree widgets. All of these new features are available to Gtk2-Perl.

Some Basic Principles

Gtk+ employs some programming paradigms that can be a bit confusing to people who've never done GUI programming before. So before we go too far, I think it's a good idea to cover them, if only briefly.

Event-Based Programming

At the heart of GUI programming is the notion of event-based programming. Unlike writing programs for the console or the Web, your program does not run sequentially—some parts of the code will be run when the program starts, but some may not be executed until an event occurs or a signal is emitted by a widget. This may be the user clicking on a button or pressing a key or moving the mouse. Events can also be triggered by messages from the window manager telling the application to quit. Or the user could be performing a drag-and-drop action from another application.

As a result, applications using Gtk2-Perl tend to follow this structure:

  • Build the basic interface.
  • Set up callbacks for various different events and signals.
  • Enter a main loop during which the application listens for events and signals and executes the defined callbacks.

The main loop itself is provided by the Gtk+ libraries, so as a rule, there's very little need to create one yourself.

Widgets and Containers

Widgets are graphical objects that the user can interact with. A widget might be a button or an icon, a menu or a scrollbar. Gtk+ includes a large palette of widgets that perform all kinds of tasks.

In order to organize these widgets on screen, and to establish the relationships between them, containers are used to group widgets together. For example, most applications will have a toolbar with icons for different functions. These icons are widgets and are grouped together by a container widget; namely, the toolbar itself. The toolbar is also contained within the top-level window. A large number of widgets are also containers; for example, a button can contain a simple text label or an icon.

In Gtk2-Perl, all widgets are represented as Perl objects. In fact, they're also blessed hash references so that you can do inheritance and extensions and create your own custom widgets (more on that next month).

Hello World

Listing 1 is a simple "Hello World" program, using the Gtk2-Perl modules. The application that this program produces is shown in Figure 1.

The first line after the shebang loads the Gtk2 module. The -init argument after the module name is a convenience—it calls the Gtk2->init function so that you don't have to later on.

The $quit variable contains a reference to an anonymous subroutine. These references are used extensively in Gtk2-Perl as callbacks that are executed when an interface event occurs. Line 5 in Listing 1 is equivalent to

sub quit {
        exit;
}

my $quit = \&quit;

Similarly, line 8 could be changed to

$window->signal_connect('delete_event', \&quit);

The rest of the program is involved with the creation of widgets, setting their properties, building up interface by placing widgets inside other widgets, and finally displaying the application on the screen.

Lines 7-12 create a top-level window widget that will contain all the other widgets, and then sets various properties of that widget. Line 14 creates a simple text label. Lines 16 and 17 create a button, and set a callback to use when the button is clicked. Lines 19-23 create a special container called a "packing box" that is used to group widgets. In this case, it is a vertical packing box, so the widgets are stacked one below the other (a horizontal packing box is also available).

Finally, on lines 27 and 29, the widgets are rendered on screen and the main Gtk+ loop is called. Without this first line, nothing would appear on screen. Without the second, the UI would be unresponsive, as it would not respond to any events that occur.

Controlling Program Flow

A vital component of event-based programming is the concept of a program loop. All GUI systems have such a loop—during each iteration, the program checks for any events and responds to them accordingly.

You can control the Gtk+ main loop using the following functions:

Gtk2->main;

This starts the main loop. Sequentially, your program pauses at this point in the program until the main loop ends.

Gtk2->main_quit;

This tells the main loop to end. Your program will then continue as before.

There are some situations where you might want to perform an iteration outside of the main loop—for example, while your program is reading or writing a filehandle or socket, or some other process that would otherwise freeze your user interface. In such situations, you can use this code to "catch up" on any unhandled events:

Gtk2->main_iteration while (Gtk2->events_pending);

The Gtk2->main_iteration function does the job of checking for and responding to any events that might have cropped up since the last check. Unsurprisingly, Gtk2->events_pending returns a true value if there are any events that haven't been handled.

Basic Widgets and Their Properties

Widgets all have a common set of methods that they inherit from their base class, Gtk2::Widget. There are far too many widgets and properties to discuss here, but the most important ones are worth taking a look at. Full information about the available widgets can be found in the Gtk+ API documentation and the Gtk-Perl tutorial; see the the "More Information" section at the end of the article.

Common Widget Methods

my $widget = Gtk2::Widget->new;

Since all Gtk+ widgets are Perl objects, they need to be constructed and, as expected, the new() method is the constructor. Most widgets can be created without any arguments, but a few will require various parameters to be set when they're constructed. If you aren't sure, use the new() method without arguments and run your program—if an argument is missing, you will be told.

$widget->show;
$widget->show_all;
$widget->hide;
$widget->hide_all;

A widget has to be rendered onto the display before it can be used—the show() method does this rendering. Every widget has to be shown, so the show_all() method is handy since it calls show() on the widget and all the widget's children. The hide() and hide_all() methods do the reverse—they remove a widget from the screen.

my $handler_id = $widget->signal_connect($signal, $callback, \@callback_data);
$widget->signal_disconnect($handler_id);

This method attaches the supplied callback reference to the given signal for the widget. There are a wide range of signals and events available—again, too many to discuss in this article. The signal_disconnect() method detaches the callback identified by the handler ID.

my $container = $widget->get_parent;

This method returns the parent widget of the widget—for example, in Listing 1, using get_parent() on the $vbox container would return $window.

Windows

Window widgets come in two types: top-level windows (Gtk2::Window), which contain the application's main interface; and dialogs (Gtk2::Dialog) that appear occasionally; for example, to ask the user a question or display configuration settings.

To create a top-level window, use the following:

my $window = Gtk2::Window->new;

A top-level window is a container, so you can use the add() method to place another widget inside it. This method is common to all container widgets (more on this in the "Containers" section).

Dialogs are similar to normal windows, but come with some extra bits and pieces to save time. Listing 2 is a simple example of Gtk2::Dialog usage. The resulting window is shown in Figure 2.

Dialog windows come prepackaged with an action area for buttons, and a vertical packing box for widgets (the vbox() method of the dialog returns the packing box). You can add buttons to the action area using the add_buttons() method, using a hash containing the label or stock ID for each button, and the response code. The response signal can be used to determine what to do when the dialog is closed or when one of the buttons is clicked. For example, if the user presses the close button on the window frame, the response code is delete-event, and we've told the dialog to use cancel for the "cancel" button and ok for "OK."

Labels

my $label = Gtk2::Label->new('This is a label.');
$label->set_text("Ain't it neat?!");
my $text = $label->get_text;

Labels are simple, rectangular widgets containing text. This text can be formatted in a number of ways. Gtk+ uses a system called "Pango" to format text, and you can use a simple HTML-like markup language to add formatting to your text labels (see Listing 3). The resulting window is shown in Figure 3.

Images

A real headache with the old Gtk+ 1.x series was the complexity involved in displaying images. In version 2.x, image support has been vastly improved. Gtk+ now directly supports a wide range of image formats and features, including GIF animation and PNG's alpha channels.

my $image = Gtk2::Image->new_from_file('icon2.png');
$image->set_from_file('icon2.png');
$image->set_from_animation(Gtk2::Gdk::PixbufAnimation 	->new_from_file('animation.gif', my $error));

Additionally, you can access Gtk+'s wide range of stock icons:

my $image = Gtk2::Image->new_from_stock('gtk-quit');
$image->set_from_stock('gtk-ok');

A complete list of all the stock icons can be found in the Gtk+ reference (see "More Information" for links).

Buttons

Buttons can be created in a number of ways. First, you can call the constructor with a string argument as a text label:

my $button = Gtk2::Button->new('Click Me!');

or you can request a stock button:

my $button = Gtk2::Button->new_from_stock('gtk-ok');

The button will use the appropriate stock icon and use the standard label for that icon (e.g., "OK" or "Quit"). This label is internationalized automatically based on the user's language settings.

To use your own child widget, simply call the constructor without arguments:

my $button = Gtk2::Button->new;

$button->add($widget);

Most of the time, you'll want to use the clicked signal to connect a callback to a button, like so:

$button->signal_connect('clicked', \&clicked);

sub clicked {
        my $button = shift;
        print "button was clicked\n";
}

Text Inputs

This creates a simple, single-line text input:

my $entry = Gtk2::Entry->new;
$entry->set_text('Enter your name here.');
my $text = $entry->get_text;

You can connect the activate signal to a Gtk2::Entry widget to catch when the user presses the Enter key.

Containers

Containers are widgets that can hold other widgets. They are vital for creating an organized interface because they define the relationships between elements on screen.

Common Container Methods

$container->add($widget);
$container->remove($widget);

Most containers support the add() and remove() methods. They simply add a widget to a container or remove it. In general, there are two kinds of containers: Those that can hold just a single widget (for example, a button) and are derived from the Gtk2Bin base widget; and those that can hold more than one. The add() and remove() methods are common to this first kind. Multiple containers usually have their own methods for adding widgets.

my @child_widgets = $container->get_children;

This returns an array of all the widgets that are held by a widget. Single-widget containers also have the child() method.

Packing Boxes

Packing boxes allow you to align widgets in horizontal rows or vertical columns. They are among the most useful containers—you will probably find that your applications will make heavy use of them. Listing 4 demonstrates the use of packing boxes and produces the output in Figure 4.

When the user clicks the button, the horizontal packing box is removed from the window and is replaced with a vertical packing box.

$box->pack_start($widget, $expand, $fill, $padding);
$box->pack_end($widget, $expand, $fill, $padding);

To add a widget to a packing box, use either pack_start() or pack_end(). The former places the widget in the next slot to the left (or top, in the case of a vertical packing box); the latter places the widget into the next slot to the right (or bottom).

The remaining three arguments define the widget's appearance. Setting $expand to 1 (or any true value) will cause the packing box to grow to fill all the space available. If $fill is true, then the widgets inside the packing box are expanded to fill all the space available. The $padding variable determines the padding (in pixels) between widgets.

Listing 5 demonstrates the different packing modes and generates the layout seen in Figure 5. In the first row, the packing box is shrunk to fit the size of the buttons. In the second, the packing box is expanded but the buttons are spread out. In the third, the buttons are expanded to fill the available size.

Tables

Tables are similar to packing boxes, but they add a second dimension. Widgets can be arranged in rows and in columns. The table size, however, is fixed—while you can add as many widgets to a packing box as you like, with a table, you must define the number of rows and columns in the table when you create it.

Listing 6 shows you how tables work. Since they are two-dimensional, you can attach widgets in many more ways. The corresponding Figure 6 shows widgets that span multiple rows and columns.

When you create a table, you must specify its size:

my $table = Gtk2::Table->new(6, 5);

The above code creates a table six rows high and five columns wide. If you try to insert a widget outside this grid, you will get an error.

The attach_defaults() method is used to add widgets to a table. There is also attach(), which gives you more fine control over the way widgets appear within the table—see the "More Information" section for details.

$table->attach_defaults($widget, $left_coord, $right_coord, $top_coord, 
                       $bottom_coord);

The four arguments to attach_defaults() after the widget are the coordinates of the left-, right-, top-, and bottom-most edges of the widget. The table grid starts in the top left at "0, 0" and extends to $x-1, $y-1, where $x and $y are the dimensions specified when you create the table.

Frames

Frames create a border around a widget with a text label as a title. Frames can only contain a single widget. An example of frame usage is shown in Listing 7, and the resulting window is shown in Figure 7.

The frame style can be controlled with the set_shadow_type() method. Allowed values are "in," "etched_in," "out," and "etched_out."

More Information

This article cannot really cover every aspect of Gtk2-Perl programming, but I hope it has given you enough of an introduction that you will want to find out more. There is certainly a steep learning curve, but once you've reached the top, I'm sure you'll find that it was worth it!

To help you on your way, I have compiled a reasonably complete list of references that should make your Gtk2-Perl programming much less painful. Also, check out Part 2 of this article next month for more on creating your own widgets.

Gtk+ Documentation

There is currently no POD documentation for Gtk2-Perl. This is because the Perl bindings adhere as closely as possible to the original C implementation. The C API is fully documented and is available in two forms:

Online. You can read the full Gtk+ documentation at http://www .gtk.org/api/. This includes documentation for the Gtk+ widget set, the Glib general purpose utility library, GdkPixbuf, and Pango.

Devhelp. Devhelp is an API documentation browser that works natively with the Gtk+ documentation system. As well as having a full index of the documentation, you can also search the function reference. It is available at http://www.imendio.com/projects/ devhelp/, and I thoroughly recommend it.

If you're not familiar with C, you may have trouble at first translating the examples from C to Perl. Fear not! The Gtk2-Perl team has documented the C to Perl mapping in the Gtk2::api POD document.

Tutorials

There are a number of tutorials that will help the learning process. Stephen Wilhelm's tutorial on the old Gtk-Perl 1.x series still works with Gtk2-Perl in 90 percent of the examples and is a pretty complete guide to the available widgets. The URL for his tutorial is http://jodrell.net/files/gtk-perl-tutorial/.

Ross McFarland, one of the Gtk2-Perl developers, has also written a tutorial that is available at http://gtk2-perl.sourceforge.net/ doc/intro/.

General Information

The main Gtk+ web site is at http://www.gtk.org/ and the Gtk2-Perl site is http://gtk2-perl.sf.net/. The Gtk2-Perl site has a great deal of extra information including documentation for Glib, as well as build utilities like ExtUtils::PkgConfig (the Perl bindings pkg-config), and information on developing XS bindings and the internals of Gtk2-Perl.

Asking Questions

Gtk2-Perl has a mailing list at http://mail.gnome.org/mailman/ listinfo/gtk-perl-list/. There is also an active IRC channel—join #gtk-perl on irc.gnome.org and ask your question.

TPJ



Listing 1

#!/usr/bin/perl
use Gtk2 -init;
use strict;

my $quit = sub { exit };

my $window = Gtk2::Window->new;
$window->signal_connect('delete_event', $quit);
$window->set_position('center');
$window->set_border_width(8);
$window->set_title('Hello World!');
$window->set_default_size(200, 100);

my $label = Gtk2::Label->new('Hello World!');

my $button = Gtk2::Button->new_from_stock('gtk-quit');
$button->signal_connect('clicked', $quit);

my $vbox = Gtk2::VBox->new;
$vbox->set_spacing(8);

$vbox->pack_start($label,  1, 1, 0);
$vbox->pack_start($button, 0, 0, 0);

$window->add($vbox);

$window->show_all;

Gtk2->main;
Back to article


Listing 2
#!/usr/bin/perl
use Gtk2 -init;
use strict;

my $dialog = Gtk2::Dialog->new;

$dialog->set_title('Example Dialog');
$dialog->set_border_width(8);
$dialog->vbox->set_spacing(8);
$dialog->set_position('center');
$dialog->set_default_size(200, 100);

$dialog->add_buttons(
        'gtk-cancel' => 'cancel',
        'gtk-ok' => 'ok'
);

$dialog->signal_connect('response', \&response);

my $label = Gtk2::Label->new('Example dialog');

$dialog->vbox->pack_start($label, 1, 1, 0);

$dialog->show_all;

$dialog->run;

sub response {
        my ($button, $response) = @_;
        print "response code is $response\n";
        exit;
}
Back to article


Listing 3
#!/usr/bin/perl
use Gtk2 -init;
use strict;

my $window = Gtk2::Window->new;
$window->signal_connect('delete_event', sub { Gtk2->main_quit });
$window->set_border_width(8);
$window->set_title('Label Example');

my $label = Gtk2::Label->new;

my $markup = <<"END";
This is some plain text.

<b>This is some bold text.</b>

<span size="small">This is some small text.</span>

<span size="large">This is some large text.</span>

<span foreground="blue">This is blue.</span> <span style="italic">This is italic.</span> <tt>This is monospace.</tt>
END

$label->set_markup($markup);

$window->add($label);

$window->show_all;

Gtk2->main;

exit;
Back to article


Listing 4
#!/usr/bin/perl
use Gtk2 -init;
use strict;

my $window = Gtk2::Window->new;
$window->set_title('Packing Box Example');
$window->signal_connect('delete_event', sub { Gtk2->main_quit });
$window->set_border_width(8);

my $label  = Gtk2::Label->new("Here's a label");
my $button = Gtk2::Button->new("Here's a button");
my $image  = Gtk2::Image->new_from_stock('gtk-dialog-info', 'dialog');

$button->signal_connect('clicked', \&clicked);

my $box = Gtk2::HBox->new;
$box->set_spacing(8);

$box->pack_start($label,  0, 0, 0);
$box->pack_start($button, 0, 0, 0);
$box->pack_start($image,  0, 0, 0);

$window->add($box);

$window->show_all;

Gtk2->main;

exit;

sub clicked {

    $window->remove($box);          # remove from the window
    foreach my $child ($box->get_children) {
            $box->remove($child);   # remove all the children
    }
    $box->destroy;                  # destroy

    # create a new box:
    my $new_box = (ref($box) eq 'Gtk2::VBox' ? Gtk2::HBox->new : 
                   Gtk2::VBox->new);

    # re-pack the widgets:
    $new_box->pack_start($label,  0, 0, 0);
    $new_box->pack_start($button, 0, 0, 0);
    $new_box->pack_start($image,  0, 0, 0);
    $new_box->show_all;

    $box = $new_box;
    $window->add($box);             # add the new box
    return 1;
}
Back to article


Listing 5
#!/usr/bin/perl
use Gtk2 -init;
use strict;

my $window = Gtk2::Window->new;
$window->set_title('Packing Box Example');
$window->signal_connect('delete_event', sub { Gtk2->main_quit });
$window->set_default_size(300, 200);
$window->set_border_width(8);

my $vbox = Gtk2::VBox->new;
$vbox->set_spacing(8);

my @values = (
        [ 0, 0 ],
        [ 1, 0 ],
        [ 1, 1 ],
);

foreach my $args (@values) {
        my $hbox = Gtk2::HBox->new;
        $hbox->set_spacing(8);
        $hbox->pack_start(Gtk2::Button->new('First Item'), @{$args}, 0);
        $hbox->pack_start(Gtk2::Button->new('Second'),     @{$args}, 0);
        $hbox->pack_start(Gtk2::Button->new('Third'),      @{$args}, 0);
        $vbox->pack_start($hbox, 0, 0, 0);
}

$window->add($vbox);

$window->show_all;

Gtk2->main;

exit;
Back to article


Listing 6
#!/usr/bin/perl
use Gtk2 -init;
use strict;

my $window = Gtk2::Window->new;
$window->set_title('Table Example');
$window->signal_connect('delete_event', sub { Gtk2->main_quit });
$window->set_border_width(8);

my $table = Gtk2::Table->new(4, 2);
$table->set_col_spacings(8);
$table->set_row_spacings(8);

# two buttons side-by-side:
$table->attach_defaults(Gtk2::Button->new('0,0 -> 1, 0'), 0, 1, 0, 1);
$table->attach_defaults(Gtk2::Button->new('1,0 -> 2, 0'), 1, 2, 0, 1);

# one button spanning two columns:
$table->attach_defaults(Gtk2::Button->new('0,1 -> 2, 1'), 0, 2, 1, 2);

# one button spanning two rows:
$table->attach_defaults(Gtk2::Button->new('0,3 -> 1, 4'), 0, 1, 2, 4);

# two more buttons to fill the space:
$table->attach_defaults(Gtk2::Button->new('1,3 -> 2, 3'), 1, 2, 2, 3);
$table->attach_defaults(Gtk2::Button->new('1,4 -> 2, 4'), 1, 2, 3, 4);

$window->add($table);
$window->show_all;

Gtk2->main;

exit;
Back to article


Listing 7
#!/usr/bin/perl
use Gtk2 -init;
use strict;

my $window = Gtk2::Window->new;
$window->set_title('Frame Example');
$window->signal_connect('delete_event', sub { Gtk2->main_quit });
$window->set_border_width(8);

my $frame = Gtk2::Frame->new('Frame Title');

$frame->set_shadow_type('etched_in');

my $button = Gtk2::Button->new('Frame Contents');
$button->signal_connect('clicked', sub { Gtk2->main_quit });
$button->set_border_width(8);

$frame->add($button);

$window->add($frame);

$window->show_all;

Gtk2->main;
Back to article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

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

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

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

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

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

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