PHP Cookbook/Client-Side PHP

From WikiContent

< PHP Cookbook
Revision as of 13:36, 7 March 2008 by Docbook2Wiki (Talk)
(diff) ←Older revision | Current revision (diff) | Newer revision→ (diff)
Jump to: navigation, search
PHP Cookbook


Contents

Introduction

PHP was created for web programming and is still used mostly for that purpose. However, newer versions of PHP are increasingly more capable as a general-purpose scripting language. Using PHP for scripts you run from the command line is especially helpful when they share code with your web applications. If you have a discussion board on your web site, you might want to run a program every few minutes or hours to scan new postings and alert you to any messages that contain certain keywords. Writing this scanning program in PHP lets you share relevant discussion-board code with the main discussion-board application. Not only does this save you time, but also helps avoid maintenance overhead down the road.

With the PHP-GTK extension, your command-line PHP programs can be full-featured GUI applications. These can also share code with PHP web applications and text-based command-line programs. Like PHP, PHP-GTK is cross-platform, so the same code runs on Unix and Windows.

The same PHP binary built to be executed as a CGI program can be run from the command line. To run a script, pass the script filename as an argument:

% php scan-discussions.php
         

On Unix, you can also use the "hash-bang" syntax at the top of your scripts to run the PHP interpreter automatically. If the PHP binary is in /usr/local/bin, make the first line of your script:

#!/usr/local/bin/php

You can then run the script just by typing its name on the command line, as long as the file has execute permission.

Command-line PHP scripts almost always use the -q flag, which prevents PHP from printing HTTP response headers at the beginning of its output:

% php -q scan-discussions.php
         

You can also use this:

#!/usr/local/bin/php -q

Another helpful option on the command line is the -c flag, which lets you specify an alternate php.ini file to load settings from. If your default php.ini file is /usr/local/lib/php.ini, it can be helpful to have a separate configuration file at /usr/local/lib/php-commandline.ini with settings such as max_execution_time = 0; this ensures that your scripts don't quit after 30 seconds. Here's how to use this alternate file:

% php -q -c /usr/local/lib/php-commandline.ini scan-discussions.php
         

You can also use this :

#!/usr/local/bin/php -q -c /usr/local/lib/php-commandline.ini

If it's likely that you'll use some of your classes and functions both for the web and for the command line, abstract the code that needs to react differently in those different circumstances, such as HTML versus plain-text output or access to environment variables that a web server sets up. A useful tactic is to make your code aware of a global variable called $COMMAND_LINE. Set this to true at the top of your command-line scripts. You can then branch your scripts' behavior as follows:

if ($GLOBALS['COMMAND_LINE']) {
  print "Database error: ".mysql_error()."\n";
} else {
  print "Database error.<br>";
  error_log(mysql_error());
}

This code not only adjusts the output formatting based on the context it's executing in (\n versus <br>), but also where the information goes. On the command line, it's helpful to the person running the program to see the error message from MySQL, but on the Web, you don't want your users to see potentially sensitive data. Instead, the code outputs a generic error message and stores the details in the server's error log for private review.

Beginning with Version 4.3, PHP builds include a command-line interface (CLI) binary.[1] The CLI binary is similar to the CGI binary but has some important differences that make it more shell-friendly. Some configuration directives have hardcoded values with CLI; for example, the html_errors directive is set to false, and implicit_flush is set to true. The max_execution_time directive is set to 0, allowing unlimited program runtime. Finally, register_argc_argv is set to true. This means you can look for argument information in $argv and $argc instead of in $_SERVER['argv'] and $_SERVER['argc']. Argument processing is discussed in Recipe 20.2 and Recipe 20.3.

The CLI binary accepts a slightly different set of arguments than the CGI binary. It doesn't accept the -q or -C flags because it does what these flags indicate by default. While -q tells the CGI binary not to print headers, the CLI binary never prints headers. Even the header( ) function produces no output under CLI. Similarly, the -C flag tells the CGI binary not to change to the directory of the script being run. The CLI binary never changes to the script directory.

The CLI binary also takes one new argument: -r. When followed by some PHP code without <?php and ?> script tags, the CLI binary runs the code. For example, here's how to print the current time:

% php -r 'print strftime("%c");'
         

Finally, the CLI binary defines handles to the standard I/O streams as the constants STDIN, STDOUT, and STDERR. You can use these instead of creating your own file handles with fopen( ) :

// read from standard in
$input = fgets(STDIN,1024);

// write to standard out
fwrite(STDOUT,$jokebook);

// write to standard error
fwrite(STDERR,$error_code);

If you're using the CLI binary, you can use php_sapi_name( ) instead of $GLOBALS['COMMAND_LINE'] to test whether a script is running in a web or command-line context:

if ('cli' == php_sapi_name()) {
  print "Database error: ".mysql_error()."\n";
} else {
  print "Database error.<br>";
  error_log(mysql_error());
}

You can use the CLI binary or the CGI binary to run programs that use the PHP-GTK extension. This extension is an interface to the GTK+ toolkit, which is a library of widgets, screen drawing code, and other necessary functions for building a GUI application.

Widgets are GUI interface elements such as buttons, scrollbars, windows, menus, and select boxes. To build a PHP-GTK application, your code must create widgets and arrange them on the screen. Recipe 20.6 shows how to create and display a widget, Recipe 20.7 shows how to arrange multiple widgets for display together, and Recipe 20.9 explains how to display a menu bar.

Widgets communicate with each other and with the rest of your program using signals. When something happens to a widget, it emits a signal; for example, when a button is clicked, it emits a clicked signal. Recipe 20.8 discusses how to capture these signals and take action when one is emitted. The sample application in Recipe 20.11 combines PHP-GTK with some SOAP function calls to display weather conditions around the world.

To install PHP-GTK on Unix, download the latest version of PHP-GTK from http://gtk.php.net/download.php and the GTK+ libraries from http://www.gtk.org/download. You also need libtool 1.4.2, automake 1.4, and autoconf 2.13 (available at http://www.gnu.org/directory/ if they're not already installed on your system).

Once you've downloaded all the necessary files and installed the support libraries and tools, unpack the PHP-GTK source distribution. In the PHP-GTK directory, run ./buildconf to create configuration files, ./configure to create makefiles, then make to build the PHP-GTK extension. Last, run make install to install the PHP-GTK extension in your PHP extensions directory. You can find detailed Unix installation instructions, including common build problems, at http://gtk.php.net/manual/en/install.unix.php.

To install PHP-GTK on Windows, no compiling is necessary. From http://gtk.php.net/download.php, you can download a compiled PHP-GTK extension and supporting libraries. Once you've downloaded and unzipped the Windows distribution, copy the files in the php4 subdirectory to your PHP binary directory (or create one if it doesn't already exist). Copy the files in the winnt\system32 subdirectory to your system32 directory (C:\WINNT\SYSTEM32 for Windows NT and Windows 2000; C:\WINDOWS\SYSTEM32 for Windows 95 and Windows 98). If you don't already have a php.ini file in place, copy the winnt\php.ini file to your Windows directory (C:\WINNT or C:\WINDOWS). If you already have a php.ini file in place, add these lines to the end of it:

[PHP-GTK]
php-gtk.extensions = php_gtk_libglade.dll, php_gtk_sqpane.dll

Detailed Windows installation instructions are at http://gtk.php.net/manual/en/install.win32.php.

On either platform, once you've installed the PHP-GTK extension, you need to use the dl( ) function to load it in any script in which you want to use GTK functionality. On Windows:

if (! class_exists('gtk')) {
    dl('php_gtk.dll');
}

On Unix:

if (! class_exists('gtk')) {
    dl('php_gtk.so');
}

If you want the same script to run unaltered on Unix or Windows, you can load the PHP-GTK extension like this:

if (! class_exists('gtk')) {
    dl('php_gtk.'. (((strtoupper(substr(PHP_OS,0,3))) == 'WIN')?'dll':'so'));
}

The GTK+ toolkit is large and powerful. PHP-GTK makes it easy to create and manipulate GTK+ objects, but designing and planning a GUI application is still a significant task. In addition to the comprehensive PHP-GTK documentation at http://gtk.php.net/manual/, also take advantage of the GTK+ documentation itself at http://developer.gnome.org/doc/API/gtk/index.html. The C class and function names in the GTK+ documentation map almost directly to their PHP equivalents. Also, the tutorial at http://www.gtk.org/tutorial/ is for GTK+ 2.0 (not 1.2), but it is still a good introduction to the concepts and practices of GTK+ application building.

Parsing Program Arguments

Problem

You want to process arguments passed on the command line.

Solution

Look in $_SERVER['argc'] for the number of arguments and $_SERVER['argv'] for their values. The first argument, $_SERVER['argv'][0], is the name of script that is being run:

if ($_SERVER['argc'] != 2) {
    die("Wrong number of arguments: I expect only 1.");
}

$size = filesize($_SERVER['argv'][1]);

print "I am $_SERVER[argv][0] and report that the size of ";
print "$_SERVER[argv][1] is $size bytes.";

Discussion

In order to set options based on flags passed from the command line, loop through $_SERVER['argv'] from 1 to $_SERVER['argc']:

for ($i = 1; $i < $_SERVER['argc']; $i++) {
    switch ($_SERVER['argv'][$i]) {
    case '-v':
        // set a flag
        $verbose = 1;
        break;
    case '-c':
        // advance to the next argument
        $i++;
        // if it's set, save the value
        if (isset($_SERVER['argv'][$i])) {
            $config_file = $_SERVER['argv'][$i];
        } else {
            // quit if no filename specified
            die("Must specify a filename after -c");
        }
        break;
    case '-q':
        $quiet = 1;
        break;
    default:
        die('Unknown argument: '.$_SERVER['argv'][$i]);
        break;
    }
}

In this example, the -v and -q arguments are flags that set $verbose and $quiet, but the -c argument is expected to be followed by a string. This string is assigned to $config_file.

See Also

Recipe 20.3 for more parsing arguments with getopt; documentation on $_SERVER['argc'] and $_SERVER['argv'] at http://www.php.net/reserved.variables.

Parsing Program Arguments with getopt

Problem

You want to parse program options that may be specified as short or long options, or they may be grouped.

Solution

Use PEAR's Console_Getopt class. Its getopt( ) method can parse both short-style options such as -a or -b and long-style options such as --alice or --bob:

$o = new Console_Getopt;

// accepts -a, -b, and -c
$opts = $o->getopt($_SERVER['argv'],'abc');

// accepts --alice and --bob
$opts = $o->getopt($_SERVER['argv'],'',array('alice','bob'));

Discussion

To parse short-style options, pass Console_Getopt::getopt( ) the array of command-line arguments and a string specifying valid options. This example allows -a, -b, or -c as arguments, alone or in groups:

$o = new Console_Getopt;
$opts = $o->getopt($_SERVER['argv'],'abc');

For the previous option string abc, these are valid sets of options to pass:

% program.php -a -b -c
% program.php -abc
% program.php -ab -c
            

The getopt( ) method returns an array. The first element in the array is a list of all of the parsed options that were specified on the command line, along with their values. The second element is any specified command-line option that wasn't in the argument specification passed to getopt( ). For example, if the previous program is run as:

% program.php -a -b sneeze
            

then $opts is:

Array
(
    [0] => Array
        (
            [0] => Array
                (
                    [0] => a
                    [1] => 
                )
            [1] => Array
                (
                    [0] => b
                    [1] => 
                )
        )
    [1] => Array
        (
            [0] => program.php
            [1] => sneeze
        )
)

Put a colon after an option in the specification string to indicate that it requires a value. Two colons means the value is optional. So, ab:c:: means that a can't have a value, b must, and c can take a value if specified. With this specification string, running the program as:

% program.php -a -b sneeze
            

makes $opts:

Array
(
    [0] => Array
        (
            [0] => Array
                (
                    [0] => a
                    [1] => 
                )
            [1] => Array
                (
                    [0] => b
                    [1] => sneeze
                )
        )
    [1] => Array
        (
            [0] => program.php
        )
)

Because sneeze is now set as the value of b, it is no longer in the array of unparsed options. Note that the array of unparsed options always contains the name of the program.

To parse long-style arguments, supply getopt( ) with an array that describes your desired arguments. Put each argument in an array element (leave off the leading --) and follow it with = to indicate a mandatory argument or = = to indicate an optional argument. This array is the third argument to getopt( ). The second argument (the string for short-style arguments) can be left blank or not, depending on whether you also want to parse short-style arguments. This example allows debug as an argument with no value, name with a mandatory value, and size with an optional value:

require 'Console/Getopt.php';
$o = new Console_Getopt;
$opts = $o->getopt($_SERVER['argv'],'',array('debug','name=','size=='));

These are valid ways to run this program:

% program.php --debug
% program.php --name=Susannah
% program.php --name Susannah
% program.php --debug --size
% program.php --size=56 --name=Susannah
% program.php --name --debug
            

The last example is valid (if counterproductive) because it treats --debug as the value of the name argument and doesn't consider the debug argument to be set. Values can be separated from their arguments on the command line by either a = or a space.

For long-style arguments, getopt( ) includes the leading -- in the array of parsed arguments; for example, when run as:

% program.php --debug --name=Susannah
            

$opts is set to:

Array
(
    [0] => Array
        (
            [0] => Array
                (
                    [0] => --debug
                    [1] => 
                )
            [1] => Array
                (
                    [0] => --name
                    [1] => Susannah
                )
        )
    [1] => Array
        (
            [0] => program.php
        )
)

We've been using $_SERVER['argv'] as the array of command-line arguments, which is fine by default. Console_Getopt provides a method, readPHPArgv( ), to look also in $argv and $HTTP_SERVER_VARS['argv'] for command-line arguments. Use it by passing its results to getopt( ):

require 'Console/Getopt.php';
$o = new Console_Getopt;
$opts = $o->getopt($o->readPHPArgv(),'',array('debug','name=','size=='));

Both getopt( ) and readPHPArgv( ) return a Getopt_Error object when these encounter an error; for example, having no option specified for an option that requires one. Getopt_Error extends the PEAR_Error base class, so you can use familiar methods to handle errors:

require 'Console/Getopt.php';
$o = new Console_Getopt;
$opts = $o->getopt($o->readPHPArgv(),'',array('debug','name=','size=='));

if (PEAR::isError($opts)) {
    print $opts->getMessage();
} else {
    // process options
}

See Also

Recipe 20.2 for parsing of program options without getopt; documentation on Console_Getopt at http://pear.php.net/manual/en/core.console.getopt.php .

Reading from the Keyboard

Problem

You need to read in some typed user input.

Solution

Use fopen( ) with the special filename php://stdin:

print "Type your message. Type '.' on a line by itself when you're done.\n";

$fh = fopen('php://stdin','r') or die($php_errormsg);
$last_line = false;  $message = '';
while (! $last_line) {
    $next_line = fgets($fp,1024);
    if (".\n" == $next_line) {
      $last_line = true;
    } else {
      $message .= $next_line;
    }
}

print "\nYour message is:\n$message\n";

If the Readline extension is installed, use readline( ):

$last_line = false; $message = '';
while (! $last_line) {
    $next_line = readline();
    if ('.' == $next_line) {
        $last_line = true;
    } else {
        $message .= $next_line."\n";
    }
}

print "\nYour message is:\n$message\n";

Discussion

Once you get a file handle pointing to stdin with fopen( ), you can use all the standard file-reading functions to process input (fread( ), fgets( ), etc.) The solution uses fgets( ), which returns input a line at a time. If you use fread( ), the input still needs to be newline-terminated to make fread( ) return. For example, if you run:

$fh = fopen('php://stdin','r') or die($php_errormsg);
$msg = fread($fh,4);
print "[$msg]";

And type in tomato and then a newline, the output is [toma]. The fread( ) grabs only four characters from stdin, as directed, but still needs the newline as a signal to return from waiting for keyboard input.

The Readline extension provides an interface to the GNU Readline library. The readline( ) function returns a line at a time, without the ending newline. Readline allows Emacs and vi-style line editing by users. You can also use it to keep a history of previously entered commands:

$command_count = 1;
while (true) {
    $line = readline("[$command_count]--> ");
    readline_add_history($line);
    if (is_readable($line)) {
        print "$line is a readable file.\n";
    }
    $command_count++;
}

This example displays a prompt with an incrementing count before each line. Since each line is added to the readline history with readline_add_history( ) , pressing the up and down arrows at a prompt scrolls through the previously entered lines.

See Also

Documentation on fopen( ) at http://www.php.net/fopen, fgets( ) at http://www.php.net/fgets, fread( ) at http://www.php.net/fread, and the Readline extension at http://www.php.net/readline; the Readline library at http://cnswww.cns.cwru.edu/php/chet/readline/rltop.html.

Reading Passwords

Problem

You need to read a string from the command line without it being echoed as it's typed; for example, when entering passwords.

Solution

On Unix systems, use /bin/stty to toggle echoing of typed characters:

// turn off echo
`/bin/stty -echo`;

// read password
$password = readline();

// turn echo back on
`/bin/stty echo`;

On Windows, use w32api_register_function( ) to import _getch( ) from msvcrt.dll:

// load the w32api extension and register _getch()
dl('php_w32api.dll');
w32api_register_function('msvcrt.dll','_getch','int');

while(true) {
    // get a character from the keyboard
    $c = chr(_getch());
    if ( "\r" == $c ||  "\n" == $c ) {
        // if it's a newline, break out of the loop, we've got our password
        break;
    } elseif ("\x08" == $c) {
        /* if it's a backspace, delete the previous char from $password */
        $password = substr_replace($password,'',-1,1);
    } elseif ("\x03" == $c) {
        // if it's Control-C, clear $password and break out of the loop
        $password = NULL;
        break;
    } else {
        // otherwise, add the character to the password
        $password .= $c;
    }
}

Discussion

On Unix, you use /bin/stty to control the terminal characteristics so that typed characters aren't echoed to the screen while you read a password. Windows doesn't have /bin/stty, so you use the W32api extension to get access _getch( ) in the Microsoft C runtime library, msvcrt.dll. The _getch( ) function reads a character without echoing it to the screen. It returns the ASCII code of the character read, so you convert it to a character using chr( ) . You then take action based on the character typed. If it's a newline or carriage return, you break out of the loop because the password has been entered. If it's a backspace, you delete a character from the end of the password. If it's a Control-C interrupt, you set the password to NULL and break out of the loop. If none of these things are true, the character is concatenated to $password. When you exit the loop, $password holds the entered password.

The following code displays Login: and Password: prompts, and compares the entered password to the corresponding encrypted password stored in /etc/passwd. This requires that the system not use shadow passwords.

print "Login: ";
$fh = fopen('php://stdin','r')   or die($php_errormsg);
$username = rtrim(fgets($fh,64)) or die($php_errormsg);

preg_match('/^[a-zA-Z0-9]+$/',$username) 
    or die("Invalid username: only letters and numbers allowed");


print 'Password: ';
`/bin/stty -echo`;
$password = rtrim(fgets($fh,64)) or die($php_errormsg);
`/bin/stty echo`;
print "\n";

// nothing more to read from the keyboard
fclose($fh);

// find corresponding line in /etc/passwd 
$fh = fopen('/etc/passwd','r')   or die($php_errormsg);
$found_user = 0;
while (! ($found_user || feof($fh))) {
    $passwd_line = fgets($fh,256);
    if (preg_match("/^$username:/",$passwd_line)) {
        $found_user = 1;
    }
}
fclose($fh);

$found_user or die ("Can't find user \"$username\"");

// parse the correct line from /etc/passwd 
$passwd_parts = split(':',$passwd_line);

/* encrypt the entered password and compare it to the password in
   /etc/passwd */
$encrypted_password = crypt($password,
                            substr($passwd_parts[1],0,CRYPT_SALT_LENGTH));

if ($encrypted_password == $passwd_parts[1]) {
    print "login successful";
} else {
    print "login unsuccessful";
}

See Also

Documentation on readline( ) at http://www.php.net/readline, chr( ) at http://www.php.net/chr, on w32api_register_function( ) at http://www.php.net/w32api-register-function, and on _getch( ) at http://msdn.microsoft.com/library/en-us/vccore98/HTML/_crt_ _getch.2c_._getche.asp; on Unix, see your system's stty(1) manpage.

Displaying a GUI Widget in a Window

Problem

You want to display a window with a GUI widget, such as a button, in it.

Solution

Create the window, create the widget, and then add the widget to the window:

// create the window
$window = &new GtkWindow();

// create the button and add it to the window
$button = &new GTKButton('Click Me, Alice');
$window->add($button);

// display the window
$window->show_all();

// necessary so that the program exits properly
function shutdown() { gtk::main_quit(); }
$window->connect('destroy','shutdown');

// start GTK's signal handling loop
gtk::main();

Discussion

First, you create a window by instantiating a new GtkWindow object. GTK objects must be created as references: &new GtkWindow( ), not new GtkWindow( ). You then create a new GtkButton object with a label "Click Me, Alice". Passing $button to the window's add( ) method adds the button to the window. The show_all( ) method displays the window and any widgets inside of it. The only widget inside the window in this example is the button. The next two lines ensure that the program quits when the window is closed. The shutdown( ) function is a callback, as is explained later in Recipe 20.8.

The last line is necessary in all PHP-GTK programs. Calling gtk::main( ) starts the signal-handling loop. This means that the program waits for signals emitted by its GUI widgets and then responds to the signals as they occur. These signals are activities like clicking on buttons, resizing windows, and typing in text boxes. The only signal this program pays attention to is the destroy signal. When the user closes the program's main window, the destroy signal is emitted, and gtk::main_quit( ) is called. This function exits the program.

See Also

Documentation on the GtkWindow class at http://gtk.php.net/manual/en/gtk.gtkwindow.php, on GTKContainer::add( ) at http://gtk.php.net/manual/en/gtk.gtkcontainer.method.add.php, on GtkWidget::show_all( ) at http://gtk.php.net/manual/en/gtk.gtkwidget.method.show_all.php, on the GtkButton class at http://gtk.php.net/manual/en/gtk.gtkbutton.php, on gtk::main_quit( ) at http://gtk.php.net/manual/en/gtk.method.main_quit.php, on and gtk::main( ) at http://gtk.php.net/manual/en/gtk.method.main.php; the tutorial at http://gtk.php.net/manual/en/tutorials.hellow.php is a helpful introduction to basic GTK programming.

Displaying Multiple GUI Widgets in a Window

Problem

You want to display more than one widget in a window.

Solution

Add all of the widgets in a container, and then add the container in the window:

// create the window
$window = &new GtkWindow();

// create the container - GtkVBox aligns widgets vertically
$container = &new GtkVBox();

// create a text entry widget and add it to the container
$text_entry = &new GtkEntry();
$container->pack_start($text_entry);

// create a button and add it to the container
$a_button = &new GtkButton('Abort');
$container->pack_start($a_button);

// create another button and add it to the container
$r_button = &new GtkButton('Retry');
$container->pack_start($r_button);

// create yet another button and add it to the container
$f_button = &new GtkButton('Fail');
$container->pack_start($f_button);

// add the container to the window
$window->add($container);

// display the window
$window->show_all();

// necessary so that the program exits properly
function shutdown() { gtk::main_quit(); }
$window->connect('destroy','shutdown');

// start GTK's signal handling loop
gtk::main();

Discussion

A window is a container that can hold only one widget. To put multiple widgets in a window, you must place all widgets into a container that can hold more than one widget and then put that container in the window. This process can be nested: the widgets inside a container can themselves be containers.

In the Solution, widgets are added to a GtkVBox container, which aligns the child widgets vertically, as shown in Figure 20-1. The add( ) method adds widgets to the GtkVBox, but pack_start( ) is used instead so that the size of the container is automatically updated with each new widget.

Figure 20-1. Widgets in a GtkVBox

Widgets in a GtkVBox

GtkHBox is similar to GtkVBox. It aligns its child widgets horizontally instead of vertically. Figure 20-2 shows the four widgets from the Solution in a CtkHBox.

Figure 20-2. Widgets in a GtkHBox

Widgets in a GtkHBox

GtkTable is a more flexible layout container; it aligns its child elements on a grid:

// create the window
$window = &new GtkWindow();

// create the container with 3 rows and 2 columns
$container = &new GtkTable(3,2);

// create a text entry widget and add it to the container
$text_entry = &new GtkEntry();
$container->attach($text_entry,0,2,0,1);

// create a button and add it to the container
$a_button = &new GtkButton('Abort');
$container->attach($a_button,0,1,1,2);

// create another button and add it to the container
$r_button = &new GtkButton('Retry');
$container->attach($r_button,1,2,1,2);

// create yet another button and add it to the container
$f_button = &new GtkButton('Fail');
$container->attach($f_button,0,2,2,3);

// add the container to the window
$window->add($container);

// display the window
$window->show_all();

// necessary so that the program exits properly
function shutdown() { gtk::main_quit(); }
$window->connect('destroy','shutdown');

// start GTK's signal handling loop
gtk::main();

Widgets are added to a GtkTable container with the attach( ) method. The first argument to attach( ) is the widget to add, and the next four arguments describe where in the grid to put the widget. The second and third arguments are the starting and ending columns for the widget. The fourth and fifth arguments are the starting and ending rows for the widget. For example:

$container->attach($text_entry,0,2,0,1) 

means that the text-entry widget starts in column zero and ends in column two, spanning two columns. It starts at row zero and ends at row one, so it spans only one row. Rows and columns are numbered beginning with zero. The text entry and button widgets aligned in a GtkTable container are shown in Figure 20-3.

Figure 20-3. Widgets in a GtkTable

Widgets in a GtkTable

See Also

Documentation on containers at http://gtk.php.net/manual/en/gtk.containers.whatare.php, the GtkVBox class at http://gtk.php.net/manual/en/gtk.gtkvbox.php, the GtkHBox class at http://gtk.php.net/manual/en/gtk.gtkhbox.php, GtkBox::pack_start( ) at http://gtk.php.net/manual/en/gtk.gtkbox.method.pack_start.php, the GtkTable class at http://gtk.php.net/manual/en/gtk.gtktable.php, and GtkTable::attach( ) at http://gtk.php.net/manual/en/gtk.gtktable.method.attach.php .

Responding to User Actions

Problem

You want to do something when a user clicks a button, chooses an item from a dropdown list, or otherwise interacts with a GUI widget.

Solution

Write a callback function and then associate the callback function with a signal using the connect( ) method:

// create the window
$window = &new GtkWindow();

// create a button with the current time as its label
$button = &new GtkButton(strftime('%c'));

// set the update_time() function as the callback for the "clicked" signal
$button->connect('clicked','update_time');

function update_time($b) {
    // the button's text is in a child of the button - a label widget
    $b_label = $b->child;
    // set the label text to the current time
    $b_label->set_text(strftime('%c'));
}

// add the button to the window
$window->add($button);

// display the window
$window->show_all();

// necessary so that the program exits properly
function shutdown() { gtk::main_quit(); }
$window->connect('destroy','shutdown');

// start GTK's signal handling loop
gtk::main();

Discussion

The code in the Solution displays a window with a button in it. On the button is the time, rendered by strftime('%c') . When the button is clicked, its label is updated with the current time.

The update_time( ) function is called each time the button is clicked because $button->connect('clicked','update_time') makes update_time( ) the callback function associated with the button's clicked signal. The first argument to the callback function is the widget whose signal triggered the call as its first argument. In this case, that means that $button is passed to update_time( ). You tell connect( ) to pass additional arguments to the callback by passing them to connect( ) after the callback function name. This example displays a window with a button and a separate label. The time is printed in the label and updated when the button is clicked:

// create the window
$window = &new GtkWindow();

// create a container for the label and the button
$container = &new GtkVBox();

// create a label showing the time
$label = &new GtkLabel(strftime('%c'));

// add the label to the container
$container->pack_start($label);

// create a button
$button = &new GtkButton('Update Time');

/* set the update_time() function as the callback for the "clicked" signal
   and pass $label to the callback */
$button->connect('clicked','update_time',$label);

function update_time($b,$lb) {
    $lb->set_text(strftime('%c'));
}

// add the button to the container
$container->pack_start($button);

// add the container to the window
$window->add($container);

// display the window
$window->show_all();

// necessary so that the program exits properly
function shutdown() { gtk::main_quit(); }
$window->connect('destroy','shutdown');

// start GTK's signal handling loop
gtk::main();

Because $label is on the list of arguments passed to $button->connect( ), $label is passed to update_time( ). Calling set_text( ) on $label updates the text displayed in the label.

See Also

Documentation on signals and callbacks at http://gtk.php.net/manual/en/gtk.signals.php, on GtkObject::connect( ) at http://gtk.php.net/manual/en/gtk.gtkobject.method.connect.php, and on GtkButton's clicked signal at http://gtk.php.net/manual/en/gtk.gtkbutton.signal.clicked.php.

Displaying Menus

Problem

You want to display a menu bar at the top of a GTK window.

Solution

Create a GtkMenu . Create individual GtkMenuItem objects for each menu item you want to display and add each menu item to the GtkMenu with append( ). Then, create a root menu GtkMenuItem with the label that should appear in the menu bar (e.g., "File" or "Options"). Add the menu to the root menu with set_submenu( ) . Create a GtkMenuBar and add the root menu to the menu bar with append( ). Finally, add the menu bar to the window:

// create the window
$window = &new GtkWindow();

// create a menu
$menu = &new GtkMenu();

// create a menu item and add it to the menu
$menu_item_1 = &new GtkMenuItem('Open');
$menu->append($menu_item_1);

// create another menu item and add it to the menu
$menu_item_2 = &new GtkMenuItem('Close');
$menu->append($menu_item_2);

// create yet another menu item and add it to the menu
$menu_item_2 = &new GtkMenuItem('Save');
$menu->append($menu_item_2);

// create a root menu and add the existing menu to it
$root_menu = &new GtkMenuItem('File');
$root_menu->set_submenu($menu);

// create a menu bar and add the root menu to it
$menu_bar = &new GtkMenuBar();
$menu_bar->append($root_menu);

// add the menu bar to the window
$window->add($menu_bar);

// display the window
$window->show_all();

// necessary so that the program exits properly
function shutdown() { gtk::main_quit(); }
$window->connect('destroy','shutdown');

// start GTK's signal handling loop
gtk::main();

Discussion

A menu involves a hierarchy of quite a few objects. The GtkWindow (or another container) holds the GtkMenuBar. The GtkMenuBar holds a GtkMenuItem for each top-level menu in the menu bar (e.g., "File," "Options," or "Help"). Each top-level GtkMenuItem has a GtkMenu as a submenu. That submenu contains each GtkMenuItem that should appear under the top-level menu.

As with any GTK widget, a GtkMenuItem object can have callbacks that handle signals. When a menu item is selected, it triggers the activate signal. To take action when a menu item is selected, connect its activate signal to a callback. Here's a version of the button-and-label time display from Recipe 20.8 with two menu items: "Update," which updates the time in the label, and "Quit," which quits the program:

// create the window
$window = &new GtkWindow();

// create a container for the label and the button
$container = &new GtkVBox();

// create a menu
$menu = &new GtkMenu();

// create a menu item and add it to the menu
$menu_item_1 = &new GtkMenuItem('Update');
$menu->append($menu_item_1);

// create another menu item and add it to the menu
$menu_item_2 = &new GtkMenuItem('Quit');
$menu->append($menu_item_2);

// create a root menu and add the existing menu to it
$root_menu = &new GtkMenuItem('File');
$root_menu->set_submenu($menu);

// create a menu bar and add the root menu to it
$menu_bar = &new GtkMenuBar();
$menu_bar->append($root_menu);

// add the menu to the container
$container->add($menu_bar);

// create a label showing the time
$label = &new GtkLabel(strftime('%c'));

// add the label to the container
$container->pack_start($label);

// create a button
$button = &new GtkButton('Update Time');

/* set the update_time() function as the callback for the "clicked" signal
   and pass $label to the callback */
$button->connect('clicked','update_time',$label);

function update_time($b,$lb) {
    $lb->set_text(strftime('%c'));
}

// add the button to the container
$container->pack_start($button);

// when the Update menu item is selected, call update_time()
$menu_item_1->connect('activate','update_time',$label);

// when the Quit menu item is selected, quit
$menu_item_2->connect('activate','shutdown');

// add the container to the window
$window->add($container);

// display the window
$window->show_all();

// necessary so that the program exits properly
function shutdown() { gtk::main_quit(); }
$window->connect('destroy','shutdown');

// start GTK's signal handling loop
gtk::main();

Callbacks are connected to the menu items with their connect( ) methods. The callbacks are connected to the activate signals towards the end of the code because the call to $menu_item_1->connect( ) passes $label to update_time( ) . For $label to be successfully passed to update_time( ) while the program is running, connect( ) has to be called after $label is instantiated.

See Also

Documentation on the GtkMenu class at http://gtk.php.net/manual/en/gtk.gtkmenu.php, GtkMenuShell::append( ) at http://gtk.php.net/manual/en/gtk.gtkmenushell.method. append.php, the GtkMenuItem class at http://gtk.php.net/manual/en/gtk.gtkmenuitem. php, GtkMenuItem::set_submenu( ) at http://gtk.php.net/manual/en/gtk.gtkmenuitem. method.set_submenu.php, GtkMenuItem's activate signal at http://gtk.php.net/manual/en/gtk.gtkmenuitem.signal.activate.php, and the GtkMenuBar class at http://gtk.php.net/manual/en/gtk.gtkmenubar.php.

Program: Command Shell

The command-shell.php program shown in Example 20-1 provides a shell-like prompt to let you execute PHP code interactively. It reads in lines using readline( ) and then runs them with eval( ). By default, it runs each line after it's typed in. In multiline mode (specified with -m or --multiline), however, it keeps reading lines until you enter . on a line by itself; it then runs the accumulated code.

Additionally, command-shell.php uses the Readline word-completion features to more easily enter PHP functions. Enter a few characters and hit Tab to see a list of functions that match the characters you've typed.

This program is helpful for running snippets of code interactively or testing different commands. The variables, functions, and classes defined in each line of code stay defined until you quit the program, so you can test different database queries, for example:

% php -q command-shell.php
[1]> require 'DB.php';

[2]> $dbh = DB::connect('mysql://user:pwd@localhost/phpc');

[3]> print_r($dbh->getAssoc('SELECT sign,planet,start_day FROM zodiac WHERE element 
            LIKE "water"'));
Array
(
    [Cancer] => Array
        (
            [0] => Moon
            [1] => 22
        )
    [Scorpio] => Array
        (
            [0] => Mars
            [1] => 24
        )
    [Pisces] => Array
        (
            [0] => Neptune
            [1] => 19
        )
)

The code for command-shell.php is in Example 20-1.

Example 20-1. command-shell.php

// Load the readline library
if (! function_exists('readline')) {
    dl('readline.'. (((strtoupper(substr(PHP_OS,0,3))) == 'WIN')?'dll':'so'))
        or die("Readline library required\n");
}

// Load the Console_Getopt class
require 'Console/Getopt.php';

$o = new Console_Getopt;
$opts = $o->getopt($o->readPHPArgv(),'hm',array('help','multiline'));

// Quit with a usage message if the arguments are bad
if (PEAR::isError($opts)) {
    print $opts->getMessage();
    print "\n";
    usage();
}

// default is to evaluate each command as it's entered
$multiline = false;

foreach ($opts[0] as $opt) {
    // remove any leading -s
    $opt[0] = preg_replace('/^-+/','',$opt[0]);

    // check the first character of the argument
    switch($opt[0][0]) {
    case 'h':
        // display help
        usage();
        break;
    case 'm':
        $multiline = true;
        break;
    }
}

// set up error display
ini_set('display_errors',false);
ini_set('log_errors',true);

// build readline completion table
$functions = get_defined_functions();
foreach ($functions['internal'] as $k => $v) {
    $functions['internal'][$k] = "$v(";
}
function function_list($line) {
    return $GLOBALS['functions']['internal'];
}
readline_completion_function('function_list');

$cmd = '';
$cmd_count = 1;

while (true) {
    // get a line of input from the user
    $s = readline("[$cmd_count]> ");
    // add it to the command history
    readline_add_history($s);
    // if we're in multiline mode:
    if ($multiline) {
        // if just a "." has been entered
        if ('.' == rtrim($s)) {
            // eval() the code
            eval($cmd);
            // clear out the accumulated code
            $cmd = '';
            // increment the command count
            $cmd_count++;
            // start the next prompt on a new line
            print "\n";
        } else {
            /* otherwise, add the new line to the accumulated code
               tacking on a newline prevents //-style comments from
               commenting out the rest of the lines entered
            */
            $cmd .= $s."\n";;
        }
    } else {
        // if we're not in multiline mode, eval() the line
        eval($s);
        // increment the command count
        $cmd_count++;
        // start the next prompt in a new line
        print "\n";
    }
}

// display helpful usage information
function usage() {
    $my_name = $_SERVER['argv'][0];

    print<<<_USAGE_
Usage: $my_name [-h|--help] [-m|--multiline]

  -h, --help: display this help
  -m, --multiline: execute accumulated code when "." is entered
                   by itself on a line. The default is to execute
                   each line after it is entered.    

_USAGE_;
    exit(-1);
}

Program: Displaying Weather Conditions

The gtk-weather.php program shown in Example 20-2 uses SOAP and a weather web service to display weather conditions around the world. It incorporates a number of GTK widgets in its interface: menus, keyboard accelerators, buttons, a text entry box, labels, scrolled windows, and columned lists.

To use gtk-weather.php, first search for weather stations by typing a search term in the text-entry box and clicking the Search button. Searching for weather stations is shown in Figure 20-4.

Figure 20-4. Searching for weather stations

Searching for weather stations

Once you've retrieved a list of weather stations, you can get the conditions at a specific station by selecting the station and clicking the Add button. The station code and its current conditions are added to the list at the bottom of the window. You can search again and add more stations to the list. The gtk-weather.php window with a few added stations is shown in Figure 20-5.

Figure 20-5. Added weather stations

Added weather stations

The web service this program uses is called GlobalWeather ; look for more information about it at http://www.capescience.com/webservices/globalweather/index.shtml.

Example 20-2. gtk-weather.php

// Load the GTK extension
dl('php_gtk.'. (((strtoupper(substr(PHP_OS,0,3))) == 'WIN')?'dll':'so'));

// Load the SOAP client class
require 'SOAP/Client.php';

// Create the main window and set its title and size
$window = &new GtkWindow();
$window->set_title('PHP Cookbook GTK Demo');
$window->set_default_size(500,200);

// The main layout container for the window is a VBox
$vbox = &new GtkVBox();
$window->add($vbox);

// Create a GtkAccelGroup to hold keyboard accelerators
$accelgroup = &new GtkAccelGroup();
$window->add_accel_group($accelgroup);

// Build the menu, starting with the GtkMenuBar. The arguments to 
// pack_start() prevent the menu bar from expanding if the window does.
$menubar = &new GtkMenuBar();
$vbox->pack_start($menubar, false, false);

// Create the "File" menu and its keyboard accelerator
$menu_file_item = &new GtkMenuItem('_File');
$menu_file_item_label = $menu_file_item->child;
$menu_file_item->add_accelerator('activate',$accelgroup,
                                 $menu_file_item_label->parse_uline('_File'),
                                 GDK_MOD1_MASK,0);
// Add the "File" menu to the menu bar
$menubar->add($menu_file_item);

// Create the submenu for the options under "File"
$menu_file_submenu = &new GtkMenu();
$menu_file_item->set_submenu($menu_file_submenu);

// Create the "Quit" option under "File" and its accelerator
// GDK_MOD1_MASK means that the accelerator is Alt-Q, not Q
// GTK_ACCEL_VISIBLE means that the accelerator is displayed in the menu
$menu_file_choices_quit = &new GtkMenuItem('_Quit');
$menu_file_choices_quit_label = $menu_file_choices_quit->child;
$menu_file_choices_quit->add_accelerator('activate',$accelgroup,
    $menu_file_choices_quit_label->parse_uline('_Quit'),GDK_MOD1_MASK,
    GTK_ACCEL_VISIBLE);

// Add the "File | Quit" option to the "File" submenu
$menu_file_submenu->append($menu_file_choices_quit);

// Create the "Help" menu and its keyboard accelerator
$menu_help_item = &new GtkMenuItem('_Help');
$menu_help_item_label = $menu_help_item->child;
$menu_help_item->add_accelerator('activate',$accelgroup,
                                 $menu_help_item_label->parse_uline('_Help'),
                                 GDK_MOD1_MASK,0);
// Add the "Help" menu to the menu bar
$menubar->add($menu_help_item);

// Create the submenu for the options under "Help"
$menu_help_submenu = &new GtkMenu();
$menu_help_item->set_submenu($menu_help_submenu);

// Create the "About" option under "Help" and its accelerator
$menu_help_choices_about = &new GtkMenuItem('_About');
$menu_help_choices_about_label = $menu_help_choices_about->child;
$menu_help_choices_about->add_accelerator('activate',$accelgroup,
    $menu_help_choices_about_label->parse_uline('_About'),GDK_MOD1_MASK,
    GTK_ACCEL_VISIBLE);

// Add the "Help | About" option to the "Help" submenu
$menu_help_submenu->append($menu_help_choices_about);

// Layout the weather station searching widgets in a GtkTable
$table_1 = &new GtkTable(2,4);
$vbox->pack_start($table_1);

// Put a label on the left in the first row
$label_sn = &new GtkLabel('Station Name: ');
$label_sn->set_alignment(1,0.5);
$table_1->attach($label_sn,0,1,0,1, GTK_FILL);

// Put a text entry field in the middle of the first row
// The accelerator allows you to hit "Return" in the field to submit
$entry_sn = &new GtkEntry();
$entry_sn->add_accelerator('activate',$accelgroup,GDK_KEY_Return,0,0);
$table_1->attach($entry_sn,1,2,0,1, GTK_FILL);

// Put a scrolled window in the second row of the table
$scrolledwindow_1 = &new GtkScrolledWindow();
$scrolledwindow_1->set_policy(GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
$table_1->attach($scrolledwindow_1,0,4,1,2, GTK_EXPAND | GTK_SHRINK | GTK_FILL,
                 GTK_EXPAND | GTK_SHRINK | GTK_FILL);

// Put a columned list in the scrolled window. By putting the list inside
// the scrolled window instead of directly in the GtkTable, the window doesn't
// have to grow to a huge size to let you see everything in the list
$clist_sn = &new GtkCList(4,array('Code','Name','Region','Country'));
$scrolledwindow_1->add($clist_sn);

// Set the columns in the list to resize automatically
for ($i = 0; $i < 4; $i++) { $clist_sn->set_column_auto_resize($i,true); }

// Add a "Search" button to the first row
$button_search =&new GtkButton('Search');
$table_1->attach($button_search,2,3,0,1, GTK_FILL);

// Add an "Add" button to the first row
$button_add = &new GtkButton('Add');
$table_1->attach($button_add,3,4,0,1, GTK_FILL);

// Layout the weather conditions display widgets in another GtkTable
$table_2 = &new GtkTable(2,3);
$vbox->pack_start($table_2);

// Add a label displaying how many stations are shown
$label_st = &new GtkLabel('Stations: 0');
$label_st->set_alignment(0,0.5);
$table_2->attach($label_st,0,1,0,1, GTK_FILL);

// Add a button to update a single station
$button_update_sel = &new GtkButton('Update Selected');
$table_2->attach($button_update_sel,1,2,0,1, GTK_FILL);

// Add a button to update all stations
$button_update_all = &new GtkButton('Update All');
$table_2->attach($button_update_all,2,3,0,1, GTK_FILL);

// Add a columned list to hold the weather conditions at the stations
// This columned list also goes inside a scrolled window
$scrolledwindow_2 = &new GtkScrolledWindow();
$scrolledwindow_2->set_policy(GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
$table_2->attach($scrolledwindow_2,0,3,1,2, GTK_EXPAND | GTK_SHRINK | GTK_FILL,
                 GTK_EXPAND | GTK_SHRINK | GTK_FILL);
$clist_st = &new GtkCList(5,array('Code','Temp','Precipitation','Wind','Updated'));
$scrolledwindow_2->add($clist_st);

// Set the columns in the list to resize automatically
for ($i = 0; $i < 5; $i++) { $clist_st->set_column_auto_resize($i,true); }

// Connect signals to callbacks

// Clicking on the "Search" button or hitting Return in the text entry field
// searches for weather stations whose name match the entered text
$button_search->connect('clicked','wx_searchByName',$entry_sn,$clist_sn,$window);
$entry_sn->connect('activate','wx_searchByName',$entry_sn,$clist_sn,$window);

// Clicking on the "Add" button adds the weather station to the bottom 
// columned list
$button_add->connect('clicked','cb_add_station',$clist_sn,$clist_st,$label_st);

// Clicking on the "Update Selected" button updates the bottom columned list
// for a single station
$button_update_sel->connect('clicked','wx_update_report',$clist_st,$label_st,
                            'selected');
// Clicking on the "Update All" button updates all stations in the bottom
// columned list
$button_update_all->connect('clicked','wx_update_report',$clist_st,$label_st,
                            'all');

// Closing the window or selecting the "File | Quit" menu item exits the program
$window->connect('destroy','cb_shutdown');
$menu_file_choices_quit->connect('activate','cb_shutdown');

// Selecting the "Help | About" menu item shows an about box
$menu_help_choices_about->connect('activate','cb_about_box',$window);

// These callbacks keep track of the currently selected row (if any)
// in each columned list
$clist_sn->connect('select-row','cb_clist_select_row');
$clist_sn->connect('unselect-row','cb_clist_unselect_row');
$clist_st->connect('select-row','cb_clist_select_row');
$clist_st->connect('unselect-row','cb_clist_unselect_row');

// The interface has been set up and the signals we want to pay attention
// to have been connected to callbacks. Time to display the window and start
// the GTK signal handling loop.
$window->show_all();
gtk::main();

/*
 * CALLBACKS AND OTHER SUPPORT FUNCTIONS
 */

// use the searchByName() function over SOAP to get a list of stations
// whose names match the given search term
function wx_searchByName($button,$entry,$clist,$window) {
    // instantiate a new SOAP client
    $sc = new SOAP_Client('http://live.capescience.com/ccx/GlobalWeather');

    $search_term = trim($entry->get_text());
    if ($search_term) {
        // call the remote function if a search term is provided
        $res = $sc->call('searchByName',
                         array(new SOAP_Value('name','string',$search_term)),
                         'capeconnect:GlobalWeather:StationInfo',
                         'capeconnect:GlobalWeather:StationInfo#searchByName');

        // pop up an error dialog if the SOAP function fails
        if (PEAR::isError($res)) {
            error_dialog($res->getMessage(),$window);
            return false;
        }
        // pop up an error dialog if there are no matches
        if (! is_array($res)) {
            error_dialog('No weather stations found.',$window);
            return false;
        }
        // add each station and its info to the columned list
        // wrapping the calls to append() with freeze() and thaw()
        // make all of the data appear at once
        $clist->freeze();
        $clist->clear();
        foreach ($res as $station) {
            $clist->append(array($station->icao,$station->name,
                                 $station->region,$station->country));
        }
        $clist->thaw();
    } 
}

// use the getWeatherReport function over SOAP to get the weather conditions
// at a particular station
function wx_getWeatherReport($code) {
    $sc = new SOAP_Client('http://live.capescience.com/ccx/GlobalWeather');
    $res = $sc->call('getWeatherReport',
                     array(new SOAP_Value('code','string',$code)),
                     'capeconnect:GlobalWeather:GlobalWeather',
                     'capeconnect:GlobalWeather:GlobalWeather#getWeatherReport');

    if (PEAR::isError($res)) {
        error_dialog($res->getMessage());
        return false;
    } else {
        return $res;
    }
}

// add the weather report in $res to the columned list $clist
// if $row is null, the report is appended to the list
// if $row is not null, the report replaces row $row in the list
function wx_add_report($clist,$label,$res,$row = null) {

    // format the timestamp
    $timestamp = str_replace('T',' ',$res->timestamp);
    $timestamp = str_replace('Z',' GMT',$timestamp);
    $timestamp = strftime('%H:%M:%S %m/%d/%Y',strtotime($timestamp));

    // format the wind information
    $wind = sprintf("%.2f m/s from %s",
                    $res->wind->prevailing_speed,
                    $res->wind->prevailing_direction->compass);
        
    $clist->freeze();
    if (! is_null($row)) {
        // replace the information in row number $row
        $clist->set_text($row,1,$res->temperature->string);
        $clist->set_text($row,2,$res->precipitation->string);
        $clist->set_text($row,3,$wind);
        $clist->set_text($row,4,$timestamp);
    } else {
        // add the information to the end of the columned list
        $clist->append(array($res->station->icao,
                             $res->temperature->string,
                             $res->precipitation->string,
                             $wind,
                             $timestamp));

        // update the columned list's internal row count
        $rows = 1 + $clist->get_data('rows');
        $clist->set_data('rows',$rows);
        // update the label that displays a station count
        $label->set_text("Stations: $rows");
    }
    $clist->thaw();
}

// update conditions for one station or all stations, depending on $mode
function wx_update_report($button,$clist,$label,$mode) {
    switch ($mode) {
    case 'selected':

        // if there is a row selected
        $selected_row = $clist->get_data('selected_row');
        if (($selected_row >= 0) && (! is_null($selected_row))) {
            $code = $clist->get_text($selected_row,0);
            
            // get the report and update the columned list
            if ($res = wx_getWeatherReport($code)) {
                wx_add_report($clist,$label,$res,$selected_row);
            } 
        }
        break;
    case 'all':
        // for each row in the columned list
        for ($i = 0, $j = $clist->get_data('rows'); $i < $j; $i++) {
            // get the report and update the list
            if ($res = wx_getWeatherReport($clist->get_text($i,0))) {
                wx_add_report($clist,$label,$res,$i);
            }
        }
        break;
    }
}

// add a station to the bottom list of weather reports
function cb_add_station($button,$clist,$clist_2,$label) {
    $selected_row = $clist->get_data('selected_row');
    // if there's a selected row in the top list of stations
    if ($selected_row >= 0) {
        $code = $clist->get_text($selected_row,0);
        // get the weather report for that station
        if ($res = wx_getWeatherReport($code)) {
            // find the row if this code is already in the list
            $row = null;
            for ($i = 0, $j = $clist_2->get_data('rows'); $i < $j; $i++) {
                if ($clist_2->get_text($i,0) == $code) {
                    $row = $i;
                }
            }
            // add the station and its report to the bottom list of
            // reports (or update the existing row)
            wx_add_report($clist_2,$label,$res,$row);
        } 
    }
}

// update a columned list's internal selected row value when a row is selected
function cb_clist_select_row($clist,$row,$col,$e) {
    $clist->set_data('selected_row',$row);
}

// clear a columned list's internal selected row value when a row is unselected
function cb_clist_unselect_row($clist) {
    $clist->set_data('selected_row',-1);
}

// display the "About Box"
function cb_about_box($menu_item,$window) {
    $about_box = &new GtkDialog();
    $vbox = $about_box->vbox;
    $action_area = $about_box->action_area;
    $about_box->set_title('About');
    $label = &new GtkLabel("This is the PHP Cookbook PHP-GTK Demo.");
    $button = &new GtkButton('OK');
    $button->connect('clicked','cb_dialog_destroy',$about_box);
    $vbox->pack_start($label);
    $action_area->pack_start($button);
    $about_box->set_modal(true);
    $about_box->set_transient_for($window);
    $about_box->show_all();
}

// display an error dialog box
function error_dialog($msg,$window) {
    $dialog = &new GtkDialog();
    $vbox = $dialog->vbox;
    $action_area = $dialog->action_area;
    $dialog->set_title('Error');
    $label = &new GtkLabel("Error: $msg");
    $button = &new GtkButton('OK');
    $button->connect('clicked','cb_dialog_destroy',$dialog);
    $vbox->pack_start($label);
    $action_area->pack_start($button);
    $dialog->set_modal(true);
    $dialog->set_transient_for($window);
    $dialog->show_all();
}

// close a dialog box
function cb_dialog_destroy($button,$dialog) {
    $dialog->destroy();
}

// quit the main program
function cb_shutdown() { gtk::main_quit(); }

Notes

  1. The CLI binary can be built under 4.2.x versions by explicitly configuring PHP with --enable-cli.
Personal tools