The view: GtkTreeView, GtkTreeViewColumn, GtkCellRenderer

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.

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.

Example 10.3. TreeView and Renderer

$view = new GtkTreeView($model);
$renderer = new GtkCellRendererText();
$column = new GtkTreeViewColumn("Folder", $renderer, "text", 1);
$view->append_column($column);

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.

There are several GtkCellRenderers in the Gtk+ library, and you could write your own, if those are not enough.

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 docs for details.

Example 10.4. Selection Changed Signal

$selection = $view->get_selection();
$selection->connect("changed", "display_selected_folder");

This code connects the "changed" signal to a function (display_selected_folder()) that displays the contents of a selected folder.

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.

Example 10.5. Drag-and-Drop reordering

$view->set_reorderable(true);

This is all it takes to make the widget drag-and-drop enabled.

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.

Example 10.6. Manual reordering

$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']);

This code moves a node from old_iter to be the last child of the new_parent node.

At the time this tutorial was written/ported there was 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. In version 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.

Example 10.7. Example source code

<?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();
?>