TreeView Tutorial

[email protected] ("Brian Tipton") Fri, 17 Nov 2006 16:43:06 -0600
Newsgroups php.gtk.doc
Message-ID <[email protected]>
Attached is a GtkTreeView tutorial ported to PHP-GTK2 from PyGTK.
Original can be found here:

http://liw.iki.fi/liw/texts/gtktreeview-tutorial.html

As this is my first attempt, I am sure there are errors.  Please
correct me where I have made mistakes and I'll do my best to get them
fixed as soon as possible.

Please respond to this address as I am not subscribed to the list.

Thanks,
Brian
treeview.xml (text/xml, 13.2 KB)
<?xml version="1.0" encoding="utf-8" ?>
<!-- $Revision: 1.7 $ -->
<chapter id="tutorials.treeview">
 <title>GtkTreeView T4utorial (basic)</title>
 
  <para>
   Thanks to Lars Wirzenius for the 
   <ulink url="http://liw.iki.fi/liw/texts/gtktreeview-tutorial.html">original version</ulink>
   of this tutorial written for PyGtk. Changes to the document were only to
   reflect the current state of the Gtk+ codebase and to be applicable to PHP.
  </para>
  
  <simpara>
   Version 2.0 of Gtk+ introduces its third generation of tree
   and list widgets. Version 1.0 had GtkList and GtkTree, version 1.2
   had GtkCList and GtkCTree, and now version 2.0 has GtkTreeView,
   which servers both as a list and a tree. With each version, the
   power and usefulness of the widgets have increased.
  </simpara>
  
  <simpara>
   GtkTreeView uses a Model/View/Controller approach. This
   means that the code is divided into a data structure representing
   user's data ("the model"), the widgets that display the data and
   interact with the user ("the view"), and some logic to tie things
   nicely together ("the controller"). The model is implemented by
   GtkTreeModel (actually, classes implementing that interface), the
   view by GtkTreeView with some helpers, and the controller by the
   user code.
  </simpara>
  
  <simpara>
   This sounds unnecessarily complicated, but the complexity
   is local, and this aproach actually simplifies overall program
   structure. For example, it is often necessary to view the same data
   (i.e., model) in different ways, or in different windows. Think,
   for example, of a programmer's editor: the same source code may
   be viewed in several windows at the same time, and changes in one
   window should be shown in all the others, as well. Thus, it makes
   sense to separate the storage of the text from its display, rather
   than storing the text in each window widget.
  </simpara>
  
  <simpara>
   The example application in this article lets the user manage
   a folder tree. The folders are virtual, not real directories in
   the filesystem, to keep the code simpler. The example is actually
   derived from Lodju, in which the folders have nothing to do with
   the filesystem.
  </simpara>
  
  <para>
   The <ulink url="http://developer.gnome.org/doc/API/">official Gtk+ 2.0 API reference documentation for GtkTreeView</ulink>
   should be read together with this tutorial, even if it is a bit sparse in
   some details.
  </para>
  
  <simpara>
   I thank the people on the Gtk+ developer IRC channel for
   encouragement and feedback.
  </simpara>
  
  <para>
   <note>The model: GtkTreeModel, GtkTreeStore</note>
  </para>
  
  <simpara>
   GtkTreeModel is the interface definition for the model part of
   GtkTreeView.  The application programmer could define his own model,
   but GtkTreeView provides the two most popular ones: a linear list
   with GtkListStore, and a hierarchical tree with GtkTreeStore. The
   GtkTreeView reference manual claims that few application programmers
   will need to use anything else, and they may well be right. Since I
   have little experience with GtkListStore, this article will discuss
   GtkTreeStore only.
  </simpara>
  
  <simpara>
   The first step in using GtkTreeView, then, is to set up a
   GtkTreeStore to keep your data. The model contains some number of
   rows and each row contains the same number of columns. The cells
   in each column contain the same type of data. These columns are
   declared when the model is created, though they can be changed later,
   if necessary.
  </simpara>
  
  <example>
   <title>Model</title>
   <programlisting role="php"><![CDATA[$model = new GtkTreeStore(Gtk::TYPE_PHP_VALUE, Gtk::TYPE_STRING);]]></programlisting>
  </example>
  
  <simpara>
   Here we create a GtkTreeStore with two columns. The first
   column contains a PHP variable (of any type), the second a
   string. There are several different types available, see the
   GTK Enums.  We will be using the first column to hold an array
   containing all the information about a folder and the second
   column to display its name.
  </simpara>
  
  <simpara>
   Note difference in naming. GtkTreeView is the C type
   name and GtkTreeStore is the PHP class that corresponds
   to GtkTreeView.  For more information, see the PHP-GTK2
   documentation.
  </simpara>
  
  <simpara>
   Note that it does not matter in which order you define
   the columns.  The order in which GtkTreeStore knows them does not
   affect how they are displayed to the user. It is not even necessary
   to display all columns to the user.
  </simpara>
  
  <simpara>
   Nodes can be added to a GtkTreeStore in several ways. My
   preference is to use the gtk_tree_store_insert_before function,
   known as the insert_before method in PHP.
  </simpara>
  
  <example>
   <title>Create Row</title>
   <programlisting role="php"><![CDATA[$folder = new_folder();
$iter = $model->insert_before(null, null);
$model->set($iter, 0, $folder);
$model->set($iter, 1, $folder['name']);]]></programlisting>
  </example>
  
  <simpara>
   This code first inserts an empty row into the model,
   then sets the values in both cells for the row.
  </simpara>
  
  <simpara>
   A GtkTreeModel lets the programmer refer to rows in various
   ways.  We see here a GtkTreeIter object, which essentially works as a
   pointer to the row.  We then use this pointer, plus a column number,
   to refer to a cell.
  </simpara>
  
  <simpara>
   GtkTreeIter objects are also used as arguments to
   gtk_tree_store_insert_before, to define the parent of the new node,
   and the sibling that is to follow the new node. If the parent is NULL,
   as in the example above, the new node is added to the top level,
   and if sibling is NULL, then the new node becomes
   the last child of the parent.
  </simpara>
  
  <simpara>
   The GtkTreeIter objects may be temporary, and may become
   invalidated as the tree is modified by adding or removing nodes. It
   is probably a bad idea to store them.
  </simpara>
  
  <simpara>
   You can also remove rows (gtk_tree_store_remove a.k.a. $model->remove()).
   For other operations, please see the API reference documentation.
  </simpara>
  
  <para>
   <note>The view: GtkTreeView, GtkTreeViewColumn, GtkCellRenderer</note>
  </para>
  
  <simpara>
   The GtkTreeView widget is the view in the Module/View/Controller
   approach.  It takes care of displaying the data stored in a model
   (GtkTreeStore or GtkListStore) to the user. You can have several
   GtkTreeViews for a single model, and changes to the model will be
   displayed immediately in all of them.
  </simpara>
  
  <simpara>
   To use GtkTreeView, you need to create the widget itself, then create
   GtkTreeViewColumns for the columns to display and GtkCellRenderers
   to tell the columns how to display a cell in the column.
  </simpara>
  
  <example>
   <title>TreeView and Renderer</title>
   <programlisting role="php"><![CDATA[$view = new GtkTreeView($model);
$renderer = new GtkCellRendererText();
$column = new GtkTreeViewColumn("Folder", $renderer, "text", 1);
$view->append_column($column);]]></programlisting>
  </example>
  
  <simpara>
   This code creates a new GtkTreeView and attaches it to a model.
   Then it creates a text cell renderer and a column and adds those to
   the view. "Folder" is the name of the column, displayed at the top
   of the column.
  </simpara>
  
  <simpara>
   There are several GtkCellRenderers in the Gtk+ library, and
   you could write your own, if those are not enough.
  </simpara>
  
  <simpara>
   The user's selections in a GtkTreeView (per view) are tracked
   using the GtkTreeSelection object. If your code needs to notice when
   the user changes the selection, connect to the GtkTreeSelection's
   "changed" signal.  You can also have the selection object call a
   function for each selected node, or programmatically change the
   selection. See the API for details.
  </simpara>
  
  <example>
   <title>Selection Changed Signal</title>
   <programlisting role="php"><![CDATA[$selection = $view->get_selection();
$selection->connect("changed", "display_selected_folder");]]></programlisting>
  </example>
  
  <simpara>
   This code connects the "changed" signal to a function (display_selected_folder())
   that displays the contents of a selected folder.
  </simpara>
  
  <simpara>
   You need to tell the GtkTreeView explicitly that the user is
   allowed to rearrange the folder tree using drag-and-drop. Fortunately,
   after telling this once, the widget takes care of the rest.
  </simpara>
  
  <example>
   <title>Drag-and-Drop Reorder</title>
   <programlisting role="php"><![CDATA[$view->set_reorderable(true);]]></programlisting>
  </example>
  
  <simpara>
   This is all it takes to make the widget drag-and-drop enabled.
  </simpara>
  
  <simpara>
   The same thing can also be done by manually should you choose not to
   enable drag-and-drop by removing the child node from the tree and inserting it
   back in as a child of another node.
  </simpara>
  
  <example>
   <title>Manual Reorder</title>
   <programlisting role="php"><![CDATA[$folder = $model->get_value($old_iter, 0);
$model->remove($old_iter);
$new_iter = $model->insert_before($new_parent, null);
$model->set($new_iter, 0, $folder);
$model->set($new_iter, 1, $folder['name']);]]></programlisting>
  </example>
  
  <simpara>
   This code moves a node from old_iter to be the last child
   of the new_parent node.
  </simpara>
  
  <simpara>
   At the time this tutorial was written/ported there is a bug in PHP-GTK2 that
   parameter order for insert_before() and insert_after() has been switched. The
   above code will not work (nor the example code below) without switching the
   order of the parameters.  As of php-gtk-2.0.0 alpha the order is still (sibling,
   parent) and it should be (parent, sibling). This has already been changed in CVS.
  </simpara>
  
  <example>
   <title>Example Source Code</title>
   <programlisting role="php"><![CDATA[<?php
// This is an example for demonstrating use of the GtkTreeView widget.
// The code in this example is not particularly good: it is written to
// concentrate on widget usage demonstration, not for maintainability.

$view = null;
$choose_parent_view = null;
$dialog = null;

function move ($old_iter=null, $new_parent, $model)
{
    if ($old_iter)
    {
        $folder = $model->get_value($old_iter, 0);
        $model->remove($old_iter);
        $new_iter = $model->insert_before($new_parent, null);
        $model->set($new_iter, 0, $folder);
        $model->set($new_iter, 1, $folder['name']);
    }
}

function dialog_ok ($args)
{
    global $dialog, $choose_parent_view, $view;
    
    $dialog->hide();
    
    list($model, $parent_iter) = $choose_parent_view->get_selection()->get_selected();
    list($model, $old_iter) = $view->get_selection()->get_selected();
    
    if ($parent_iter && $old_iter)
    {
        move($old_iter, $parent_iter, $model);
    }
}

function dialog_cancel ($args)
{
    global $dialog;
	
    $dialog->hide();
}

function choose_parent ($args)
{
    global $dialog;

    $dialog->show();
}

function move_to_bottom ($args)
{
    global $view;
    
    list ($model, $old_iter) = $view->get_selection()->get_selected();
    
    if ($old_iter)
    {
        move($old_iter, null, $model);
    }
}

function quit ($args)
{
    Gtk::main_quit();
}

function make_view ($model)
{
    $view = new GtkTreeView($model);
    $view->set_reorderable(true);
    $renderer = new GtkCellRendererText();
    $column = new GtkTreeViewColumn("Folder", $renderer, "text", 1);
    $view->append_column($column);
    $view->show();
    
    $scrolled = new GtkScrolledWindow();
    $scrolled->add($view);
    $scrolled->show();
    
    return array($view, $scrolled);
}

function make_buttons ($list)
{
    $buttonbox = new GtkHBox();
    
    foreach ($list as $label => $func)
    {
        $button = new GtkButton();
        $button->set_label($label);
        $button->connect("clicked", $func);
        $button->show();
        $buttonbox->pack_start($button, false, false);
    }
    
    $buttonbox->show();
    
    return $buttonbox;
}

$model = new GtkTreeStore(Gtk::TYPE_PHP_VALUE, Gtk::TYPE_STRING);

for ($i=0; $i<100; $i++)
{
    $folder = array("name" => "folder $i", $files => array("foo", "bar"));
    $iter = $model->insert_before(null, null);
    $model->set($iter, 0, $folder);
    $model->set($iter, 1, $folder['name']);
}

list($view, $scrolled) = make_view($model);
$view->set_reorderable(true);

$buttons = array
(
    "Quit"           => "quit",
    "Choose parent"  => "choose_parent",
    "Move to bottom" => "move_to_bottom"
);

$buttonbox = make_buttons($buttons);

$vbox = new GtkVBox();
$vbox->pack_start($buttonbox, false, false);
$vbox->pack_start($scrolled, true, true);
$vbox->show();

$win = new GtkWindow(Gtk::WINDOW_TOPLEVEL);
$win->connect("delete_event", "quit");
$win->add($vbox);
$win->show();
$win->resize(300, 500);

list($choose_parent_view, $scrolled) = make_view($model);

$buttons = array
(
    "OK"     => "dialog_ok",
    "Cancel" => "dialog_cancel"
);

$buttonbox = make_buttons($buttons);

$vbox = new GtkVBox();
$vbox->pack_start($scrolled, true, true);
$vbox->pack_start($buttonbox, false, false);
$vbox->show();

$dialog = new GtkWindow(Gtk::WINDOW_TOPLEVEL);
$dialog->set_default_size(200, 400);
$dialog->add($vbox);

Gtk::main();
>?]]></programlisting>
  </example>
</chapter>