home | O'Reilly's CD bookshelfs | FreeBSD | Linux | Cisco | Cisco Exam  


Book HomePHP CookbookSearch this book

20.11. 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

Figure 20-4. 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

Figure 20-5. 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(); }



Library Navigation Links

Copyright © 2003 O'Reilly & Associates. All rights reserved.