Hello World (advanced)

This tutorial deals with some more advanced functionality than the first hello world tutorial. Here, we create a login screen in which the user can type his username and his password. When the user clicks on the Login button, the text fields are checked and an error message is shown if the user forgot his name or his password.

Here is a screenshot of how the program will look when it's finished:

At first, we create a window, set its title and tell that the main loop shall be quit when the window is destroyed - you already know that from the first tutorial:

$wnd = new GtkWindow();
$wnd->set_title('Login');
$wnd->connect_simple('destroy', array('gtk', 'main_quit'));

The next task is to create all the widgets we need on this dialog: At first, two text entry fields for username and password:

$txtUsername = new GtkEntry();
$txtPassword = new GtkEntry();

Then we want the user to know which text field is password or username, so there is the need for some descriptive labels:

//The second parameter says that the underscore should be parsed as underline
$lblUsername = new GtkLabel('_Username', true);
$lblPassword = new GtkLabel('_Password', true);

And at last, we need two buttons: One for login, and one to cancel the process:

$btnLogin    = new GtkButton('_Login');
$btnCancel   = new GtkButton('_Cancel');

You might note that the GtkLabel constructor gets a second parameter passed, to tell that the underscore _ is shown as underline to the next character, but that's not done in the GtkButton constructor: The button constructor does that automatically, while the label one doesn't. This behavior might be strange to you, but it's more likely that you have a mnemonic in a button's label than that a normal label has one.

Now the labels need to know what widget needs to be activated when they are activated via the mnemonic. This is done that way:

$lblUsername->set_mnemonic_widget($txtUsername);
$lblPassword->set_mnemonic_widget($txtPassword);

Further, we need to determine what action is taken when the buttons are clicked. The Cancel button shall destroy the window (thus ending the application) and the Login button shall check the text fields and take further action:

$btnCancel->connect_simple('clicked', array($wnd, 'destroy'));
$btnLogin ->connect_simple('clicked', 'login', $wnd, $txtUsername, $txtPassword);

We pass the three widgets $wnd, $txtUsername and $txtPassword as optional parameters to the (yet to be defined) login function, because we will need them there: To get the values from the text fields, and to destroy/hide the window if all is ok.

Now that we have all the widgets we need, they are added to the window. As GtkWindow is a bin container, it can hold only one single widget. So we need a container that can hold several widgets and lays out our elements nicely. The decision goes to GtkTable, because it provides a way to keep the labels in a column, and the entry fields in the next one:

$tbl = new GtkTable(3, 2);
$tbl->attach($lblCredit, 0, 2, 0, 1);
$tbl->attach($lblUsername, 0, 1, 1, 2);
$tbl->attach($txtUsername, 1, 2, 1, 2);
$tbl->attach($lblPassword, 0, 1, 2, 3);
$tbl->attach($txtPassword, 1, 2, 2, 3);

(The $lblCredit is just a label with a message). The buttons get their own GtkHButtonBox, because that class allows one to lay out several buttons very nicely:

$bbox = new GtkHButtonBox();
$bbox->set_layout(Gtk::BUTTONBOX_EDGE);
$bbox->add($btnCancel);
$bbox->add($btnLogin);

The last thing in layouting is adding the table and the button box to the window. That's not directly possible, because GtkWindow is a GtkBin and can hold only one widget. So we need another container, this time a GtkVBox:

$vbox = new GtkVBox();
$vbox->pack_start($tbl);
$vbox->pack_start($bbox);

That would be all, and we can show the window:

$wnd->add($vbox);
$wnd->show_all();
Gtk::main();

The last thing missing is the login function that is called when the user clicks the Login button. It shall check the value of the username and password field: If their length is 0 (the user didn't type anything in), an error message will be shown. If all is ok, the window will be destroyed and the program could go on with loading the main window.

Getting the text from the widgets is very simple, we just use the get_text() method of GtkEntry:

$strUsername = $txtUsername->get_text();
$strPassword = $txtPassword->get_text();

Checking is done via the normal php strlen function. If an error occured, we want to show a message box with a little message. GtkMessageDialog is ideal for this, as it automatically creates icons and the buttons (Ok, Yes/No) automatically:

$dialog = new GtkMessageDialog($wnd, Gtk::DIALOG_MODAL, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, $errors);
$dialog->set_markup("The following errors occured:\r\n<span foreground='red'>" . $errors . "</span>");
$dialog->run();
$dialog->destroy();

Now make sure that you put the login function before(!) the main loop and run it.

Example 3.1. The full program

<?php
/**
*   Here we create a login window.
*   It has a username and a password field, and a
*   Cancel and Login button. Some error checking
*   is being done when the user clicks "Login".
*/

if (!class_exists('gtk')) {
    die("Please load the php-gtk2 module in your php.ini\r\n");
}

/**
*   This function gets called as soon as the user 
*   clicks on the Login button.
*
*   @param GtkWindow $wnd           The login window, needed to close it
*                                    when all is ok
*   @param GtkEntry $txtUsername    The username text field, used to get
*                                    the username
*   @param GtkEntry $txtPassword    The password widget to retrieve the
*                                    password
*/
function login(GtkWindow $wnd, GtkEntry $txtUsername, GtkEntry $txtPassword)
{
    //fetch the values from the widgets into variables
    $strUsername = $txtUsername->get_text();
    $strPassword = $txtPassword->get_text();

    //Do some error checking
    $errors = null;
    if (strlen($strUsername) == 0) {
        $errors .= "Username is missing.\r\n";
    }
    if (strlen($strPassword) == 0) {
        $errors .= "No password given.\r\n";
    }

    if ($errors !== null) {
        //There was at least one error.
        //We show a message box with the errors
        $dialog = new GtkMessageDialog($wnd, Gtk::DIALOG_MODAL,
            Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, $errors);
        $dialog->set_markup(
            "The following errors occured:\r\n"
            . "<span foreground='red'>" . $errors . "</span>"
        );
        $dialog->run();
        $dialog->destroy();
    } else {
        //No error. You would need to hide the dialog now
        //instead of destroying it (because when you destroy it,
        //Gtk::main_quit() gets called) and show the main window
        $wnd->destroy();
    }
}

//Create the login window
$wnd = new GtkWindow();
$wnd->set_title('Login');
//Close the main loop when the window is destroyed
$wnd->connect_simple('destroy', array('gtk', 'main_quit'));


//Set up all the widgets we need
$lblCredit   = new GtkLabel('Please provide your data');
//The second parameter says that the underscore should be parsed as underline
$lblUsername = new GtkLabel('_Username', true);
$lblPassword = new GtkLabel('_Password', true);
$txtUsername = new GtkEntry();
$txtPassword = new GtkEntry();
$btnLogin    = new GtkButton('_Login');
$btnCancel   = new GtkButton('_Cancel');


//Which widget should be activated when the 
// mnemonic (Alt+U or Alt+P) is pressed?
$lblUsername->set_mnemonic_widget($txtUsername);
$lblPassword->set_mnemonic_widget($txtPassword);
//Hide the password
//$txtPassword->set_invisible_char('*');

//Destroy the window when the user clicks Cancel
$btnCancel->connect_simple('clicked', array($wnd, 'destroy'));
//Call the login function when the user clicks on Login
$btnLogin->connect_simple('clicked', 'login', $wnd, $txtUsername, $txtPassword);


//Lay out all the widgets in the table
$tbl = new GtkTable(3, 2);
$tbl->attach($lblCredit, 0, 2, 0, 1);
$tbl->attach($lblUsername, 0, 1, 1, 2);
$tbl->attach($txtUsername, 1, 2, 1, 2);
$tbl->attach($lblPassword, 0, 1, 2, 3);
$tbl->attach($txtPassword, 1, 2, 2, 3);


//Add the buttons to a button box
$bbox = new GtkHButtonBox();
$bbox->set_layout(Gtk::BUTTONBOX_EDGE);
$bbox->add($btnCancel);
$bbox->add($btnLogin);


//Add the table and the button box to a vbox
$vbox = new GtkVBox();
$vbox->pack_start($tbl);
$vbox->pack_start($bbox);

//Add the vbox to the window
$wnd->add($vbox);
//Show all widgets
$wnd->show_all();
//Start the main loop
Gtk::main();
?>