Introduction to PHP-GTK 2

Creating Desktop Applications with PHP

Scott Mattocks

Crisscott.com

Introduction to PHP-GTK 2

What is PHP-GTK 2?

  • Where did it come from?
    • Arose from a need for better interface for CLI scripts
    • Natural progression from CLI scripts
    • Provides a GUI for CLI scripts

Why use PHP-GTK 2?

  • Why use PHP-GTK 2?
    • Not all applications need to be on a web server.
      • IDE
      • Spreadsheet
      • Chat
    • Not everyone is connected to the Internet all the time
    • Easier to maintain state (no need for sessions)
    • Direct access to file system

Terminology

The Simplest Application

Produces:
 
 <?php
 $window = new GtkWindow();
 $window->connect_simple('destroy', array('Gtk', 'main_quit'));
 $window->show_all();
 Gtk::main();
 ?>
 
  • Anatomy of an application
    1. Define application elements and relationship to one another
    2. Tell application how to react to certain events
    3. Hand contol over to PHP-GTK and wait for events to occur

Adding a GtkEntry

 
<?php
// Create a window.
$window = new GtkWindow();
// Set it up to close cleanly.
$window->connect_simple('destroy', array('Gtk', 'main_quit'));
// Create an entry to get the search term.
$entry = new GtkEntry();
// Add the entry to the window.
$window->add($entry);
// Show the window and all of its contents.
$window->show_all();
// Start the main loop.
Gtk::main();
?>
 
  • Trying to add a second widget to window will fail (GtkWindow descends from GtkBin)
  • Why do you have to add a container to a container?
    • Everything in PHP-GTK is highly specialized
    • The primary function of a bin is not to organize many children
    • Specialized multi-child containers rarely do more than organize layout

Parents and Children

  • What is a container?
    • Provides a context for the child
    • Tells child where to appear and how to behave (expand, fill, padding)
    • A widget can only have one parent. Otherwise it would be in two places at once.
  • Why have bins?
    • Wrap an individual widget to provide some extra display (GtkFrame)
    • Allows widgets to specialize. Managing one child is easier especially when you have other things to worry about (GtkWindow)

Multi-Child Container Classes

  • Boxes
    • Boxes are normally nested.
    • Two HBoxes in a VBox allows for two rows of widgets but doesn't allow columns to be aligned easily
    • Hard to keep things looking nice when window is resized.
  • Table
    • Very similar to HTML tables, but specifically designed for layout not tabular data
    • Children are attached to cells
    • Cells can span multiple rows or columns
    • Dimensions of a cell depend on other cells in row/column
  • Notebook
    • One page shown at a time, all others hidden
    • Tabs can be moved to any side of notebook or hidden
    • Used to easily swap out visible elements
  • Fixed
    • Similar to CSS position: absolute
    • Children placed (x, y) pixels from upper left corner
    • Children do not move as window is resized
    • Used for precision positioning where window is not resizeable (splash screen)

Using a GtkTable for Layout

 
<?php
// Create a window.
$window = new GtkWindow();
// Set it up to close cleanly.
$window->connect_simple('destroy', array('Gtk', 'main_quit'));
// Create a new table.
$table = new GtkTable(2, 3);
// A little short cut.
$expandFill = Gtk::EXPAND | Gtk::FILL;
// Attach a place holder for the menu.
$table->attach(new GtkFrame('Menu'), 0, 2, 0, 1, $expandFill, 0, 0, 0);
// Attach a place holder for the toolbar.
$table->attach(new GtkFrame('Toolbar'), 0, 2, 1, 2, $expandFill, 0, 0, 0);
// Attach a place holder for the main section.
$table->attach(new GtkFrame('Main'), 0, 1, 2, 3);
// Attach a place holder for the weather section.
$table->attach(new GtkFrame('Weather'), 1, 2, 2, 3);
// Add the table to the window.
$window->add($table);
// Show the window and all of its contents.
$window->show_all();
// Start the main loop.
Gtk::main();
?>
 
  • Table is meant for organization. Nothing added to children like GtkFrame or GtkWindow.
  • Arguments 2-5 define placement
  • Other arguments define expand, fill and padding in x/y diretions
  • Size of window and cells will change when real contents are added
  • Elements are added to the table, then table is added to window (example of nested containers)

Filling out the UI

View Source
  • Replace GtkFrames with actual elements. Full details are inappropriate for an "intro".
  • Show scripts/uiComplete.php
  • Identify elements that were added
  • At this point we just have a fancy somewhat interactive image. Need to make it responsive by adding signal handlers.

Making it Interactive

Events & Signals

  • Widgets emit a predefined set of signals in response to events.
    • Example: Clicking a button causes that particular button to emit the "clicked" signal
    • Widget can be set up to take a specific action when it emits a specific signal
    • Example: Send SOAP request when button A is clicked
  • Main loop checks event queue, pops an event, calls signal handlers, iterates
    • Main loop starts and checks event queue
    • TextEntry A says, "Hey, my text value was changed!"
    • Main loop checks to see if anything should be done when TextEntry A's text value chagnes. No. Nobody cares. Throw away event. Iterate loop
    • Button B says, "Hey, I was clicked!"
    • Main loop checks to se if anything should be done when Button B is clicked. Yes. Main loop calls all callbacks for this widget and signal as defined by signal handlers. Throw away event. Iterate loop
    • Event queue is empty. Iterate loop. Repeat.

Creating a Signal Handler

Example Signal Handlers

 
// ...
// Create the main menu bar.
$this->menu = new GtkMenuBar();

// Add a file menu.
$file = new GtkMenuItem('_File');
$this->menu->append($file);

// Add some options to the file menu.
// We need a menu to put them in.
$fileMenu = new GtkMenu();
        
// Set the menu as the sub menu.
$file->set_submenu($fileMenu);

// Add an EVDB login option. This should open a dialog.
$login = new GtkMenuItem('Login');
$fileMenu->append($login);
        
// Make the options do something when clicked.
// The login option should open a login dialog.
$login->connect_simple('activate', array($this, 'loginDialog'));
// ...
 
  • Open application and show signal handlers for login (menu and toolbar).

Example Signal Handlers (cont'd)

 
// ...
// We need a button to initiate the search.
$search = GtkButton::new_from_stock(Gtk::STOCK_OK);
        
// Attach the button to the table.
$table->attach($search, 3, 4, 3, 4, 0, 0, 0, 0);
        
// Search for events when the user clicks the button.
// Organize things slightly.
$after  = array($afterMonths,  $afterDays,  $afterYears);
$before = array($beforeMonths, $beforeDays, $beforeYears);
$search->connect_simple('clicked', array($this, 'searchEvents'),
                        $keyword, $after, $before
                        );
// ...
 
  • Open application and show signal handler for searchEvents.
  • $keywords, $after, and $before are widget or arrays of widgets passed to callback. Must pass actual widget to get realtime value.
  • searchEvents() makes a WebService call to EVDB. Data returned is a list of events.

Models & Views

  • One model can be shown by many views
    • GtkTreeModelSort: wraps a model to sort it without altering it
    • GtkTreeModelFilter: wraps a model to hide certain rows without altering underlying model

Trees & Lists

  • Think of a list as an array and a tree as a multi-dimensional array
  • Good for displaying structured data sets such as file systems or database rows
  • Not good for data whose structure changes from one node the next (XML)
  • Crisscott_Events can make good use of GtkTreeStore and GtkTreeView to show events
  • Switch to source code

Multi-line Text

  • Very powerful
  • Too many features to discuss now

Customizing the Look & Feel

Styles

 
// ...
// We need a button to initiate the search.
$search = GtkButton::new_from_stock(Gtk::STOCK_OK);

// Make the button stand out a little.
$style = $search->get_style();
$style->bg[Gtk::STATE_NORMAL] = GdkColor::parse('#0A0A6A');
$style->fg[Gtk::STATE_NORMAL] = GdkColor::parse('#FFFFFF');
$search->set_style($style);
// ...
 
  • Show app and point out change in button.

Resource Files

  • Open source code and uncomment parse RC line.
  • Then restart app

More Information

Open floor for Q&A