A little more complexity

We haven't used most of PHP5 powerful object oriented features so far. Let's make an application that opens a text file and displays its contents to you: a simple notepad-kind-of application. Please note that is a very very simple application meant for educational purposes and has no practical functionality! Here is the code, read it, and don't be intimidated by its size. A detailed explanation follows the code:

Example 8.2. A simple notepad

<?php

class Notepad extends GtkWindow
{
    protected $currentFile;
    protected $buffer;
    protected $status;
    protected $context;
    protected $lastid;

    function __construct($fileName = null)
    {
        parent::__construct();
                
        $mainBox  = new GtkVBox();
        $textBuff = new GtkTextBuffer();
        $textView = new GtkTextView($textBuff);
        $statusBar= new GtkStatusBar();

        $mainBox->pack_start($this->buildMenu(), false, false);
        $mainBox->pack_start($textView, true, true);
        $mainBox->pack_start($statusBar, false, false);

        $this->currentFile = $fileName;
        $this->buffer = $textBuff;
        $this->status = $statusBar;

        $this->connect_simple('destroy', array($this, 'quit'));
        $this->set_title('Simple Notepad');
        $this->maximize();
        $this->add($mainBox);
        $this->show_all();

        $this->loadFile();
    }

    function buildMenu()
    {
        $menuBar  = new GtkMenuBar();
        $fileMenu = new GtkMenu();
        $menuName = new GtkMenuItem('_File');
        
        $quit = new GtkImageMenuItem('gtk-quit');
        $quit->connect_simple('activate', array($this, 'quit'));
        $quit->connect_simple('enter_notify_event', 
                                array($this, 'updateStatus'), 1);
        $quit->connect_simple('leave_notify_event', 
                                array($this, 'updateStatus'), 0);
        
        $fileMenu->append($quit);
        $menuName->set_submenu($fileMenu);
        $menuBar->add($menuName);

        return $menuBar;
    }
   
    function loadFile()
    {
        if($this->currentFile != null) {
            $contents = file_get_contents($this->currentFile);
            $this->buffer->set_text($contents);
        }
    }

    function updateStatus($enter)
    {
        if($enter) {
            $id = $this->status->get_context_id("Message");
            $lastMsg = $this->status->push($id, "Quits the Application");
            $this->context = $id;
            $this->lastid = $lastMsg;
        } else {
            $this->status->remove($this->context, $this->lastid);
        }
    }
    
    function quit()
    {
        Gtk::main_quit();
    }

}

new Notepad('simple.phpw');
Gtk::main();

?>

The Constructor

The class structure is similar to what we have seen before, except the addition of some class properties and functions. Let's first have a look at the constructor. The constructor takes a single optional argument that defaults to null. That parameter is (path of) the file name that we wish to open. If you don't pass a parameter to the constructor, it will simply open nothing.

OK, so we first construct the parent (a GtkWindow) and then create some widgets that we will use. A GtkVBox for our layout, a GtkTextBuffer and a GtkTextView to display the contents of the file and a GtkStatusBar to display some messages. We setup the layout and add the respective widgets to the VBox.

Next we set the class properties:

$this->currentFile = $fileName;
$this->buffer = $textBuff;
$this->status = $statusBar;
 
These three lines set the values for the class properties. Class properties are an essential part of all object-oriented PHP-GTK 2 applications. They are useful because you may need to access a particular widget in a function that it was not created in. When we create a widget, the object's scope is only within the function it was created in. For example, we created the status bar in the constructor of our class, but we obviously need to access it elsewhere (when we need to put some messages on it). But since the variable $statusBar can be accessed only within the constructor, we assign a class property (called status) to it. Now we can access the status bar in any function within the class by using $this->status.

Simply extend this concept to other widgets too. Basically, you need to assign class properties to widgets that you think will need the entire class as its scope. Actually, class properties can also be used efficiently to store any data that you may need across the entire class. One example of this in our applications is the currentFile property. This will simply store the path of the current file opened, or null if none is. We can then find out the name of the current file opened in any function of the class. Class properties have other uses too, one such use I can think of is a flag. For our notepad application, you may want to add a class property called saved which will have Boolean values to indicate whether the current file has been saved to disk or not.

Note that all the class properties have been defined as protected. This is simply a good object oriented practice. We don't want them to be public but we also want any classes that extends ours to be able to access them.

And finally, we set the title of the window, maximize it, add the layout and display all the widgets. And then we call the loadFile() function to display the contents of our file in the text buffer that we had created.

The buildMenu() function

Note how we had called this function from the constructor:

$mainBox->pack_start($this->buildMenu(), false, false);
 
We do that because we want to want to split our class into as many modules as possible. Instead of building our entire layout in the constructor itself, it is better to split the layout into major parts and dedicate a function for each part. Here we have a function to build the menu bar, but not for the text view or status bar because they take only 2 lines each to construct!

In this function, we just create our menu bar, add a simple file menu with a single "Quit" button to it. Let's have a look at these lines:

$quit->connect_simple('activate', array($this, 'quit'));
$quit->connect_simple('enter_notify_event', array($this, 'updateStatus'), 1);
$quit->connect_simple('leave_notify_event', array($this, 'updateStatus'), 0);
 
Here we connect the activate, enter-notify-event and leave-notify-event to their respective handlers. Look at the second parameter to the connect_simple() functions. It is an array with two elements. The first element is the special $this variable and the second element is a string. If you had read the Hello World tutorial, you would have come across a line like this:
$wnd->connect_simple('destroy', array('gtk', 'main_quit'));
 
Compare the usages and it should dawn on you that whenever you want to connect a signal to callback function which is in a class, you must specify the callback as an array, the first element of which points to the class, and the second element of which is the name of the callback. Hence, we connect the various events of our quit button to this classes' quit() and updateStatus() functions.

Sometimes, it is possible to pass parameters to functions in order to use those objects in the function. As a rule of thumb, if you have more than one function that uses a particular object, it is better to dedicate a class property to it; but if there is only a single function that requires the object, it is better to pass the object as a parameter instead. A common occurrence of this situation is while connecting signals to callbacks. Have a look at the tutorial on signals and callbacks for more on this and how to pass custom parameters to the callbacks.

Getting back to the function, we return the top-most widget in our menu: the menu bar, after adding the sub-menu to it. The constructor then receives this object and adds it to the main VBox.

The loadFile() function

This function's purpose is to load the contents of the file to be opened and display them in the text view. Fairly straight-forward, we first check whether the class property currentFile is not null, and then use set_text() on the buffer class property.

The updateStatus() function

This function serves as the callback for the enter-notify-event and leave-notify-event signals. Here we access the status bar via the status class property and add/remove a message depending on whether mouse is entering or leaving the Quit menu button.

The quit() function

This is the most simple function of all, only a single that quits the GTK main loop. You may wonder why we have a one-line function called 'quit' when we could have connected the signals directly to main_quit like:

$this->connect_simple('destroy', array('Gtk', 'main_quit'));
 
The reason for that is, in applications, you would most probably want to do some cleanup before the application quits, and this function is your chance to do it. For our simple notepad application, an addition that is possible here is to check for the saved flag (again, a class property) and prompt the user to save the file if it is not.

The last two lines:

new Notepad('simple.phpw');
Gtk::main();
 
instantiates an object of our class (and thereby opens the simple.phpw file located in the same directory) and starts the main GTK loop. You can also shift Gtk::main() to the constructor of our class.