Making a GUI Mess of PHP

Building Stand-alone GUI Applications With PHP-GTK

This article was first published in the issue of International PHP Magazine.

Let me introduce you to PHP-GTK. It’s been around for several years now, and you’ve probably already heard of it or even met it. You may have even heard what it can do—allow developers to create graphical applications using PHP. Egads! It sounds as if it’s the Holy Grail of PHP, something that can take PHP to new heights, breaking it free from the bonds of the Web. In fact, it can do just that, as I will show you with a practical application: a simple text editor.

Reintroducing PHP-GTK

I’m here to dispel a myth about PHP-GTK. I recently heard someone say they felt it was dead and dormant, that the project was being discontinued, deprecated, as it were, with the upcoming release of PHP 5. It’s true, PHP-GTK 1.0 does not work with PHP 5, and compiling the CVS version (the most up-to-date code including GTK 2) against PHP 5 is not very promising (that is, it doesn’t seem to work well), but that doesn’t mean the project is over, done with. On the contrary, a stable PHP 5 means that PHP-GTK 2 development can begin in full force.

Its development lately, though, has been slow, and the 1.0 version is still lacking much documentation. These signs are discouraging, I imagine. Still, now is the time, more than ever, to explore PHP-GTK and see how it can change the future of PHP.

Dispensing with Formalities

Let’s cut to the chase. A “hello, world” example for PHP-GTK creates a nice little window and perhaps a button that reads, you guessed it, “hello, world.” While this illustrates a few basic principles of PHP-GTK, it doesn’t provide a programmer with something practical. So, let’s say “goodbye” to “hello, world” and move forward.

In this article, we’ll explore PHP-GTK through the code of a simple text editor I like to call pedit. Don’t be fooled, though. While this text editor may be lacking in features, it is still fully functional and ready to process text documents on both Windows and *NIX systems. To run the code, you’ll need PHP 4.3.x and PHP-GTK 1.0.0.

Load It Up and Move It Out

When building an application, it is important to build it in such a way that those who will eventually use it experience as little hassle as possible when running the said application. Thus, the ultimate goal of any PHP-GTK application is portability—or it should be. However, this is more a dream than a reality, since the end-user must have both PHP and the PHP-GTK extension to execute the program. This presents quite a problem when the idea is to create an application for world domination that millions can use and enjoy.

Still, portability is something to strive for, and there are a few tools out there to help with making PHP and PHP-GTK portable. Perhaps as PHP-GTK gains in popularity, more and better tools will emerge on the scene. Until then, let’s at least make sure the gtk extension is loaded.

To ensure that the gtk extension is loaded, we will employ the use of PHP’s extension_loaded() and dl() functions, both of which are very handy when it comes to sniffing around for the existence of an extension and loading it if it’s not there. Please note, however, that dl() must be enabled in php.ini, so not all systems may be able to use it—portability shot down again.

The following code illustrates how to test for and load the extension:

if (!extension_loaded('gtk')) {
    dl('php_gtk.' . PHP_SHLIB_SUFFIX);
}

Notice the use of PHP_SHLIB_SUFFIX. This PHP predefined constant provides a shortcut around testing for the platform, and it returns the proper file extension. When this code runs, it tests whether the gtk extension is loaded. If it’s not, it will load either php_gtk.so or php_gtk.dll, depending on the system.

The only problem with this method—other than the fact that dl() may not be enabled—is that dl() only checks the extension directory as defined in php.ini. Thus, if php_gtk.so (or .dll) isn’t there, you’ll not see any GUI magic.

Moving Right Along: The Window

I’ve split up our example text editor application into four distinct parts for easier code maintenance and readability. These parts also fall into logical divisions, according to their function: the window, the menu bar, the text area, and the functions that make it all happen.

We’ll first take a look at the window, the most basic and top-level component of a PHP-GTK application. Fig. 1 illustrates how we want our window to appear when all is said and done. Listing 1 shows what’s taking place in the background.

Fig. 1. The text editor window, created with PHP-GTK
The text editor window

You can see in Listing 1 that I’ve initialized two variables at the start of my script. These are unimportant to the workings of PHP-GTK, so I’ll skip over them for now. They do, however, play an important role in the functions that make this particular application work, so take note of them.

Creating the window is as simple as

$w =& new GtkWindow();

All the real magic is done behind the scenes in the binding of GTK to PHP, but that’s a discussion for core developers.

Listing 1. Main window source (pedit)
#!/usr/local/bin/php
<?php
if (!extension_loaded('gtk')) {
dl('php_gtk.' . PHP_SHLIB_SUFFIX);
}
$loaded_file = null;
$text_changed = false;
require_once 'pedit_functions.php';
$w =& new GtkWindow();
$w->set_title('Untitled');
$w->set_usize(800, 600);
$w->connect_object('destroy', array('Gtk', 'main_quit'));
$w->connect('delete-event', create_function('', 'return false;'));
$box =& new GtkVBox();
$w->add($box);
require_once 'pedit_menu.php';
require_once 'pedit_textpad.php';
$w->show_all();
Gtk::main();
?>

The variable $w now refers to our window object, and we can take this time now to apply a few unimportant, but nice, aesthetic attributes, such as a window title (with the set_title() method) and an initial window size (notice I’ve set the window size to 800x600 with set_usize()—this has nothing to do with screen resolution). There are many other methods that affect the visual appearance of a window, such as initial placement on screen, but I’ll leave those for you to discover. What’s next on our docket (and in the code of Listing 1) are two very important and highly useful methods: connect() and connect_object().

Getting Connected, Wrangling Events

The connect() and connect_object() methods are probably the two most important methods used in our text editor application. Indeed, they’re very likely the most important methods in all of PHP-GTK. In short, these methods are what make PHP-GTK applications work.

Events abound in PHP-GTK. Every click on a menu choice, highlight, mouseover, or item activation fires an event. So, events are firing all around, and you need to pull up your boots, throw on your cowboy hat, grab a lasso, and start wrangling them. connect() and connect_object() do just that—but don’t forget your spurs.

The first parameter of each of these functions is always a string of the event name you wish to capture. The second is the action you want to take place when that event occurs. In the case of connect(), this is always a function, which can be provided in the form of a string name or through the use of PHP’s create_function() to create one on the fly, as I have done in Listing 1.

connect_object() differs from connect() in that it accepts an array as its second argument. The array contains as its first element a reference to an object (passing by reference is advisable) or the name of an object. The second element contains a method of the object.

Sometimes the usage of connect() and connect_object() are interchangeable. It depends on the programmer’s preference. For example, I have used:

$w->connect_object('destroy', array('Gtk', 'main_quit'));

But this might as well have been written as:

$w->connect('destroy', create_function('', 'Gtk::main_quit();'));

Get to know the connect() method, and you’re well on your way to taming the wild beast of events.

Watch the Fireworks, Events Are Firing All Around

As I mentioned earlier, almost every action within the window of a PHP-GTK application has an event (or “signal”) associated with it. This simple text editor application only deals with five such signals…now. We could extend the application to include many more features, but for brevity’s sake and for peace of mind (my mind and yours), we’ll only take a look at these five signals: destroy, delete-event, activate, clicked, and changed.

Let’s first take a look at the delete-event signal. This signal is fired primarily when the user clicks the X close button of the window. Most of the time, this terminates the program, and it’s terminated because the programmer chose to have it terminate using the delete-event signal.

By default, delete-event returns false. This forces delete-event to call its default event handler, the destroy() method, which, in turn, emits the destroy signal. By default, the destroy signal calls Gtk::main_quit() to safely close the application. Thus, the example in Listing 1 is a bit redundant. I don’t need to explicitly tell delete-event to return false, nor do I need to tell the destroy signal to call Gtk::main_quit(). The program would work exactly the same if I left out these two lines.

However, these illustrate an important concept, and that is: the default behavior can be overridden and the programmer can choose to modify how the program terminates by displaying shutdown messages or other interesting functionality, such as returning true on delete-event and forever trapping a user in the program—no matter how often they click that X, they can never close it that way.

Another signal, activate, fires when a user chooses a menu item from the drop-down menus. We’ll explore the menus later on, but for now, take a look at Listing 2. Almost every menu item connects activate to some sort of action. These actions all have equivalent functions found in Listing 4. A similar signal is the clicked signal, which fires when a user clicks a button.

Finally, the last signal that we’ll wrangle, is the changed signal. This signal fires when, well, something changes. Specifically, it fires when text is changed in an editable region (see Listing 3). This will come into play later when the pieces of the text editor start coming together.

We’ve explored the window. We’ve examined the concepts of signals (or events) and connecting actions to them. Now, let’s start putting these concepts into play as we move on to take a look at what’s on the menu.

While Listing 2 seems much longer than Listing 1, it’s not any more difficult to understand. In fact, most of the code is replicated several times over throughout the listing, so there are only a few lines we need to examine.

Listing 2. The menu source (pedit_menu.php)
<?php
/* Application menu bar */
$menu =& new GtkMenuBar();
$box->pack_start($menu, false, false);
/* File menu */
$file =& new GtkMenuItem('File');
$menu->append($file);
$file_menu =& new GtkMenu();
$new =& new GtkMenuItem('New');
$new->connect('activate', 'new_file');
$file_menu->append($new);
$open =& new GtkMenuItem('Open');
$open->connect('activate', 'file_open_dialog');
$file_menu->append($open);
$separator =& new GtkMenuItem();
$separator->set_sensitive(false);
$file_menu->append($separator);
$save =& new GtkMenuItem('Save');
$save->connect('activate', 'save_file');
$file_menu->append($save);
$separator =& new GtkMenuItem();
$separator->set_sensitive(false);
$file_menu->append($separator);
$close =& new GtkMenuItem('Close & Quit');
$close->connect_object('activate', array('Gtk', 'main_quit'));
$file_menu->append($close);
$file->set_submenu($file_menu);
/* Edit menu */
$edit =& new GtkMenuItem('Edit');
$menu->append($edit);
$edit_menu =& new GtkMenu();
$cut =& new GtkMenuItem('Cut');
$cut->connect('activate', 'cut_text');
$edit_menu->append($cut);
$copy =& new GtkMenuItem('Copy');
$copy->connect('activate', 'copy_text');
$edit_menu->append($copy);
$paste =& new GtkMenuItem('Paste');
$paste->connect('activate', 'paste_text');
$edit_menu->append($paste);
$separator =& new GtkMenuItem();
$separator->set_sensitive(false);
$edit_menu->append($separator);
$select_all =& new GtkMenuItem('Select All');
$select_all->connect('activate', 'select_all_text');
$edit_menu->append($select_all);
$edit->set_submenu($edit_menu);
?>

The first line of the listing creates a reference to a menu bar object easily, just like it was to create the window earlier. Storing that reference to the $menu variable allows us to take on as many menu items as needed with ease. The first few lines under the “File menu” section illustrate this.

$file =& new GtkMenuItem('File');
$menu->append($file);

And there it is. I’ve created a menu item named “File” and stored to $file. To add this item to the menu bar, just use the append() method. Menu items will appear on the menu in the order in which you append them.

Menus are simple to grasp. They have three main components: GtkMenuBar, GtkMenuItem, and GtkMenu. The menu bar is the top-level component. To that, we add menu items, such as the “File” item (later on in the code, you’ll notice that we append() an “Edit” menu, as well). For each of these menu items, we also create a GtkMenu object (for example, the one referenced by $file_menu) and append still more items to it. The GtkMenu object acts as a container for more items; it forms the basis of what will become our drop-down menu.

The following code illustrates the creation of our drop-down menu “container” object and the first menu item in the object. Notice again the use of append() to attach individual items to the menu.

$file_menu =& new GtkMenu();

$new =& new GtkMenuItem('New');
$new->connect('activate', 'new_file');
$file_menu->append($new);

So, now we have a drop-down menu named “File,” and its first item is “New.” The “New” menu choice has an activate signal that is fired when a user chooses, or clicks on, this menu item. Note what happens when activate fires: it calls the new_file() function, which is a user defined function in Listing 4. The activate signal is not the only one allowed to menu items, but it is the only one we will deal with for now.

Once all the items have been created and appended to the menu object ($file_menu), all that’s left is to set the menu as a submenu of $file. This is done with the set_submenu() method as shown here:

$file->set_submenu($file_menu);

In the end, you’ll have two drop down menus that appear similar to Figures 2 and 3.

Fig. 2. The file menu
The file menu
Fig. 3. The edit menu
The edit menu

Notice how I’ve added several separators to break up the menu items. These separators aren’t created by any special method. Create them with a GtkMenuItem that has no label and set their sensitivity to false with set_sensitive(). This ensures that the user cannot interact with the item; it’s for decoration only.

Serving Up Some Delectable Text

By now, our text editor application is shaping up. We have a window and we have our menus, but we’re still missing one of the most important features, and that is the editable text field.

Figure 1 shows what our text area should look like. Listing 3 shows how to make it work.

Listing 3. The text area source (pedit_textpad.php)
<?php
$text_area =& new GtkHBox();
$box->pack_start($text_area);
$default_font = Gdk::font_load('-*-courier-medium-r-normal--*-140-*-*-m-*-iso8859-1');
$textpad_style =& new GtkStyle();
$textpad_style->font = $default_font;
/* Editable text field */
$textpad =& new GtkText();
$textpad->set_editable(true);
$textpad->set_style($textpad_style);
$textpad->set_line_wrap(false);
$textpad->set_word_wrap(false);
$textpad->connect('changed', 'text_changed');
$text_area->pack_start($textpad);
/* Vertical scrollbar */
$textpad_vadj = $textpad->vadj;
$textpad_vscrollbar =& new GtkVScrollbar($textpad_vadj);
$text_area->pack_end($textpad_vscrollbar, false);
?>

Again, as with the earlier parts of our application—the window and the menus—there is nothing entirely complex about the text area. We’ll start by creating a reference to a GtkText object.

$textpad =& new GtkText();

Next, we’ll play around with a few settings. Of course we want to set it editable. It would defeat the purpose of a text editor if the text weren’t editable.

$textpad->set_editable(true);

Another setting I use is set_style(). This is an aesthetic setting that can be discarded without hurting the application. However, I have included it in order to set the font to a mono-spaced typeface for easier reading. To do this, pass a GtkStyle object to set_style(). However, before passing a GtkStyle object, we must first create one.

$default_font = Gdk::font_load('-*-courier-medium-r-normal--*-140-*-*-m-*-iso8859-1');
$textpad_style =& new GtkStyle();
$textpad_style->font = $default_font;

This block of code is a bit out of scope for our discussion, but, in short, Gdk::font_load() loads a font, which must be provided in X Logical Font Description (XLFD) format. The code provided should work on both Windows and Unix-type systems. Save the loaded font to a GtkStyle object and then pass it to the editor window with set_style().

The two other settings, set_line_wrap() and set_word_wrap(), have been explicitly turned off by setting them to false. Native support for text wrapping is awkward, and it is suggested that PHP’s wordwrap() be used to handle wrapping, though I do not use it in this example. Perhaps future versions of PHP-GTK will overcome the awkwardness and provide better support for wrapping.

Finally, notice that, like many of the other elements of this application, I use connect() on the $textpad to connect the changed signal (fired when the user changes text) to the text_changed() function in Listing 4. The text_changed() function basically sets a $text_changed variable to true for testing purposes during other actions, such as when the user decides to create a new file or open an existing file before saving the working document. If $text_changed is set to true, then we know to display an appropriate message so the user doesn’t lose his changes. Futhermore, text_changed() provides another aesthetic feature by modifying the window title with set_title() to reflect changes made to the text.

I want to take this time to look at a few of the functions in Listing 4 that deal specifically with the GtkText object. In particular, these functions are new_file() and open_file(), as well as cut_text(), copy_text(), paste_text(), and select_all_text(). There is no ambiguity in these names; they do what they say, and references to them may be found in Listing 2, where they are connected to specific menu items.

When loading text into a GtkText object, it’s always a good idea to call the freeze() method first, as is illustrated in new_file() and open_file(). This avoids any unsightly text flashes that the editor field may have while the text is updating, and you don’t want your editor to go around flashing everyone. Another method, set_point() places the cursor at a specific location in the editor field. In this case, we place it at the beginning so we can use forward_delete() to clear the field. Finally, in the case of open_file(), we use the insert() method, which inserts text into the field, and then we call the thaw() method to update the display.

GtkText also has some clipboard features, as shown in the cut_, copy_, and paste_text() functions. These can be used in conjunction with the select_all_text() function, which uses the select_region() method to select a specific area of text. Here, we tell it to select everything.

Listing 4. The functions that make it work (pedit_functions.php)
<?php
function new_file()
{
global $text_changed, $loaded_file, $textpad, $w;
if ($text_changed === true) {
save_close_dialog(true);
} else {
$loaded_file = null;
$textpad->freeze();
$textpad->set_point(0);
$textpad->forward_delete($textpad->get_length());
$textpad->thaw();
$w->set_title('Untitled');
}
}
function file_open_dialog()
{
global $text_changed, $loaded_file;
if ($text_changed === true) {
save_close_dialog();
} else {
$fs =& new GtkFileSelection('Select a File');
if (!is_null($loaded_file)) {
$directory = dirname($loaded_file);
/* We must provide a trailing slash, so provide the appropriate
one for the current system */
$directory .=
(strtoupper(substr(PHP_OS, 0, 3) == 'WIN')) ? '\\' : '/';
$fs->set_filename($directory);
}
$ok = $fs->ok_button;
$ok->connect('clicked', 'open_file', $fs);
$ok->connect_object('clicked', array(&$fs, 'destroy'));
$cancel = $fs->cancel_button;
$cancel->connect_object('clicked', array(&$fs, 'destroy'));
$fs->show();
}
}
function save_file()
{
global $text_changed, $loaded_file, $textpad, $w;
if (!is_null($loaded_file)) {
$bytes = file_put_contents($loaded_file, $textpad->get_chars(0, -1));
if ($bytes === false) {
save_error_dialog();
} else {
$text_changed = false;
$w->set_title($loaded_file);
}
}
else
{
file_save_dialog();
}
return true;
}
function cut_text()
{
global $textpad;
$textpad->cut_clipboard();
}
function copy_text()
{
global $textpad;
$textpad->copy_clipboard();
}
function paste_text()
{
global $textpad;
$textpad->paste_clipboard();
}
function select_all_text()
{
global $textpad;
$textpad->select_region(0, -1);
}
function open_file($button, &$fs)
{
global $textpad, $w, $loaded_file;
$textpad->freeze();
$textpad->set_point(0);
$textpad->forward_delete($textpad->get_length());
$textpad->insert(null, null, null, file_get_contents($fs->get_filename()));
$textpad->thaw();
$w->set_title($fs->get_filename());
$loaded_file = $fs->get_filename();
return true;
}
function file_save_dialog()
{
$fs =& new GtkFileSelection('Save File');
$ok = $fs->ok_button;
$ok->connect('clicked', 'file_save_from_dialog', $fs);
$ok->connect_object('clicked', array(&$fs, 'destroy'));
$cancel = $fs->cancel_button;
$cancel->connect_object('clicked', array(&$fs, 'destroy'));
$fs->show();
}
function file_save_from_dialog($button, &$fs)
{
global $w, $loaded_file;
$w->set_title($fs->get_filename());
$loaded_file = $fs->get_filename();
save_file();
return true;
}
function text_changed()
{
global $w, $text_changed, $loaded_file;
$text_changed = true;
if (!is_null($loaded_file)) {
$w->set_title('(*) ' . $loaded_file);
} else {
$w->set_title('(*) Untitled');
}
}
function save_close_dialog($new_file = false)
{
$dialog =& new GtkDialog();
$dialog->set_title('Save before closing?');
$dialog->set_usize(300, 125);
$dialog->set_position(GTK_WIN_POS_CENTER);
$dialog->connect('delete-event', create_function('', 'return false;'));
$dialog_vbox = $dialog->vbox;
$dialog_action_area = $dialog->action_area;
$label =& new GtkLabel('Would you like to save the current file first?');
$dialog_vbox->pack_start($label);
$label->show();
$ok =& new GtkButton('Ok');
if ($new_file) {
$ok->connect('clicked', create_function('', 'save_file();
new_file();'));
} else {
$ok->connect('clicked', create_function('', 'save_file();
file_open_dialog();'));
}
$ok->connect_object('clicked', array(&$dialog, 'destroy'));
$dialog_action_area->pack_start($ok);
$ok->show();
$no =& new GtkButton('No');
if ($new_file) {
$no->connect('clicked', create_function('', 'global $text_changed;
$text_changed = false; new_file();'));
} else {
$no->connect('clicked', create_function('', 'global $text_changed;
$text_changed = false; file_open_dialog();'));
}
$no->connect_object('clicked', array(&$dialog, 'destroy'));
$dialog_action_area->pack_start($no);
$no->show();
$cancel =& new GtkButton('Cancel');
$cancel->connect_object('clicked', array(&$dialog, 'destroy'));
$dialog_action_area->pack_start($cancel);
$cancel->show();
$dialog->show();
}
function save_error_dialog()
{
$dialog =& new GtkDialog();
$dialog->set_title('Error saving');
$dialog->set_usize(300, 125);
$dialog->connect('delete-event', create_function('', 'return false;'));
$dialog_vbox = $dialog->vbox;
$dialog_action_area = $dialog->action_area;
$label =& new GtkLabel('Unable to open file for saving.');
$dialog_vbox->pack_start($label);
$label->show();
$ok =& new GtkButton('Ok');
$ok->connect_object('clicked', array(&$dialog, 'destroy'));
$dialog_action_area->pack_start($ok);
$ok->show();
$dialog->show();
}
/* This function exists in PHP 5 */
if (!function_exists('file_put_contents')) {
function file_put_contents($filename, $data)
{
if (($h = @fopen($filename, 'w')) === false) {
return false;
}
if (($bytes = @fwrite($h, $data)) === false) {
return false;
}
fclose($h);
return $bytes;
}
}
?>

Presentation Is Everything

Now that the application has been laid bare and exposed, we need to take a look at a few methods I have skipped over that are important in pasting these individuals parts—the window, menu, and text area—together.

First, let’s journey back to Listing 1 and look at the GtkVBox object.

$box =& new GtkVBox();

This creates a box in which the elements are arranged vertically. That is, each time an item is added to $box, it is stacked below the previously added item. In our application, we want the menu bar to appear at the top with the editor field below it, thus we use GtkVBox as a container for these elements.

To add an item to a GtkVBox object, use pack_start() or pack_end().

$box->pack_start($menu, false, false);

This line from Listing 2, illustrates the pack_start() method. pack_start() adds items from the top and left of the container. It’s name suggests that it “packs” them, so one would assume that each item gets placed on top of the other, like packing a box. However, the opposite is true. Think of it like packing a box upside down in a weightless environment. Each item packed in goes below the previously added item. On the other hand, pack_end() works like packing a box down here on Earth. The first item in, goes on the bottom, or at the end. Each subsequent item is placed on top.

When using pack_start() or pack_end(), the first parameter is the object you wish to “pack.” In the previous example, we added the $menu object to the box. In the next example, from Listing 3, a new box container is needed to hold both the editor field and a vertical scrollbar. Since the scrollbar needs to be horizontally positioned to the right of the field, we’ll use GtkHBox for a horizontal box container.

$text_area =& new GtkHBox();
$box->pack_start($text_area);

This creates our new horizonal box, $text_area, and adds it to the main $box below the menu bar. Now, we can safely add $textpad and $textpad_vscrollbar to the $text_area box.

$text_area->pack_start($textpad);
...
$text_area->pack_end($textpad_vscrollbar, false);

Now that the items of our application are in place, we can flip some switches and let the show begin. Back in Listing 1, we must add $box, which contains all the items of the application, to the main window. This is done with the add() method. Finally, to make everything show up properly, call the show_all() method of the window object and, after that, Gtk::main(). These top off the application and make it run with flying colors.

Getting a Dialog Going

The application should be fully functional now. Just make sure all the parts are connected and give it a go. However, there are a few other items of note included in Listing 4 that we should take a look at, and these are the creation of dialog boxes and file selection dialogs.

Closely related but miles apart in functionality, dialog boxes and file selection dialogs offer much desired functionality to PHP-GTK applications. Almost every application in existence has some kind of dialog or pop-up window with a warning or prompt of some sort. Thus, they are an important concept to learn.

Listing 4 contains four functions that deal with dialogs—two are normal message pop-ups, two are file selection boxes. First, let’s examine the message pop-up created by save_close_dialog().

The save_close_dialog() function is called by new_file() and file_open_dialog(). Its primary purpose is to make sure the user doesn’t forget to save any changes to the current file before opening another one. To do this, it brings up the dialog window shown in Fig. 4.

Fig. 4. A dialog window
A dialog window

To create this window, create a new GtkDialog object. Note the use of set_title() and set_usize(), among other methods. Dialog objects are sub-widgets of GtkWindow objects, so everything available to a window is also available to a dialog. However, there are two properties of dialog windows that are not inherited from GtkWindow: vbox and action_area.

These properties define the regions of the dialog box. In vbox, a GtkVBox object, we will place a GtkLabel object. In this example, the label reads, “Would you like to save the current file first?” The action_area, a GtkHBox object, is where the buttons that provide actions, as the name implies, reside. To add a button, create a new GtkButton and connect an action to it. In save_close_dialog(), we first test to see whether the user chose to create a new file or open an existing file. Depending on the user’s selection, we connect the “Ok” and “No” button to different actions.

For each button and the label, notice that pack_start() is again used to add the items. Also, of important note, is the use of the show() method. Call this method for each object—label, buttons, and dialog—or else they won’t display.

Likewise, the file selection dialog, an object of GtkFileSelection, is a sub-widget of GtkWindow, and its creation and implementation is much the same as that of a dialog window; however, it has several more special properties, two of which this application uses: ok_button and cancel_button.

Rather than create these buttons on our own, the file selection dialog makes them a default part of its behavior. All the hard work is done for us. We simply need to connect specific actions to signals on these buttons. Take a look at file_open_dialog() for an example.

When a user selects the “File > Open” menu choice, the file selection dialog displayed in Fig. 5 appears because the activate signal on the Open menu item calls file_open_dialog(). This function first checks to see whether the text has changed (recall the text_changed() function from earlier). If it has, then it calls the aforementioned save_close_dialog(). Otherwise, it proceeds to create and display a file selection dialog.

Fig. 5. A file selection dialog
A file selection dialog

Notice that I have added a third parameter to the connect() method call on the “Ok” button:

$ok->connect('clicked', 'open_file', &$fs);

The connect() method always passes the current object (in this case, the $ok object) as a first argument to the function named in its second parameter. However, more arguments may be passed, as is the case here, where we pass a reference to the file selection object for use in the open_file() function.

The okay_ and cancel_button objects of a file selection object do not need to call the show() method, but be sure that the file selection object itself does make a call to show(), otherwise there’ll be no show.

Looking Ahead

The application is now complete, and you can do a bit more than say “hello” in a nice little window. It is my hope that you will take the concepts examined here and roll out PHP-GTK applications of your own, for that is what is needed for PHP-GTK’s own development to foster and grow: demand. I also hope that you now have a newfound appreciation for PHP itself, not as a Web scripting language, but as a general, multi-purpose language used to rapidly develop all kinds of applications, including graphical ones.

I believe PHP-GTK has a bright future ahead. This will be more evident later this year when the PHP-GTK development team begins binding the GTK+ 2.x libraries to PHP 5. I also believe that, through the use of PHP-CLI and PHP-GTK, the once-fledgling Web scripting language will take off into uncharted territories—uncharted for PHP, at least. It’s always exciting to keep an eye open on all the development taking place in the PHP community to see what will come next.


Tools Needed

  • PHP 4.3.x (CLI version recommended)
  • PHP-GTK 1.0.0
  • GTK+ v1.2.6 or greater (but not GTK+ v2.x)
  • php_win.exe is recommended for Windows systems and is available in the Windows download at gtk.php.net; the GTK libraries come bundled with the PHP-GTK Windows binary distribution

Compiling PHP scripts into executables?

It’s long been known that PHP scripts could be chmod 755 and run from the command line without first passing it through the php binary. It’s simple. On *NIX systems, just add #!/usr/local/bin/php (the path to the php binary) to the top of the script and execute it from the command line with ./example.php. Technically, this still passes it through the binary, but it allows the script to have control.

On a Windows system, the process is similar. Create a .bat file named example.bat and include the line c:\PHP\cli\php example.php in the file. Again, use the path to your installation of PHP. (Note the use of the CLI version in this example.) Make sure example.bat is in the same directory as example.php, and either double-click on example.bat or type example at a command prompt.

In both of these instances, however, the user is expected to have PHP installed on their system, including whatever extensions the application uses. What is needed is a way to distribute the PHP script/application with the binary so the user doesn’t need to have PHP installed. There was a PHPCompiler program rumored to exist in some distant past of PHP that did just this, but the developers seem to have disappeared (though you can still find the downloads available if you search on it). Yet, now there is a program to replace PHPCompiler: PriadoBlender.

PriadoBlender combines the php binary with your PHP scripts to create an executable version of your program. They even provide the PHP-GTK libraries for bundling with the application. Unfortunately, this is only available on Windows at this time, but perhaps a version for Mac and Unix/Linux will emerge in the near future.