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