I wrote several examples to show how to use TreeView as list or as
tree. I hope this will help others to avoid ask the simple question again.

The purpose of these examples is to show the contents of certain
directory.First, the simplest one, show the contents of the directory as
a list.

    use Gtk2 '-init';
    use Glib qw(TRUE FALSE);
    use Cwd;
    use constant {
        FILE_ICON  => 0,
        FILE_NAME  => 1,
        FILE_MTIME => 2,
    };

    my $dir = shift || getcwd;
    unless ( -d $dir ) {
        die "$dir is not a directory or doesn't exist!\n";
    }
    my $window = Gtk2::Window->new('toplevel');
    $window->signal_connect('delete_event' => sub { Gtk2->main_quit; });
    ret_vbox($window, $dir);
    $window->show();
    Gtk2->main;

    sub ret_vbox {
        my $top = shift;
        my $dir = shift;
        $top->set_title('Directory contents of '. $dir);
        my $vbox = Gtk2::VBox->new(FALSE, 4);
        my $sw = Gtk2::ScrolledWindow->new;
        $sw->set_size_request (600, 400);
        $sw->set_policy ('automatic', 'automatic');
        # create TreeView
        my $model = create_model($dir);
        my $treeview = Gtk2::TreeView->new_with_model($model);
        add_columns($treeview);
        $sw->add($treeview);
        $vbox->pack_start($sw,TRUE,TRUE,0);
        $vbox->show_all();
        $top->add($vbox);
    }

    sub create_model {
        my $dir = shift;
        my $model = Gtk2::ListStore->new(
            'Glib::String', 'Glib::String', 'Glib::String'
        );
        opendir(DIR, $dir) or die "Can't open directory $dir: $!";
        foreach my $file( readdir(DIR) ) {
            next if $file =~ /^[.]/; # ignore '.', '..' and hiden files
            my $iter = $model->append();
            $model->set(
                $iter,
                FILE_ICON, ( -d "$dir/$file" ? 'gtk-directory' : 'gtk-file'  
),
                FILE_NAME, $file,
                FILE_MTIME,  scalar(localtime((stat("$dir/$file"))[9]))
            );
        }
        return $model;
    }

    sub add_columns {
        my $treeview = shift;
        ## Add first column
        my $column1 = Gtk2::TreeViewColumn->new();
        $column1->set_title('file name');
        ### first cell for column one
        my $icon = Gtk2::CellRendererPixbuf->new();
        $column1->pack_start($icon, FALSE);
        $column1->set_attributes( $icon, 'stock-id' => FILE_ICON );
        ### second cell for column one
        my $name = Gtk2::CellRendererText->new();
        $column1->pack_start($name, FALSE);
        $column1->set_attributes( $name, 'text' => FILE_NAME );
        $column1->set_sort_column_id(FILE_NAME);
        $treeview->append_column($column1);
        ## Add second column
        my $column2 = Gtk2::TreeViewColumn->new();
        $column2->set_title("modify time");
        my $mtime = Gtk2::CellRendererText->new();
        $column2->pack_start($mtime, FALSE);
        $column2->set_attributes($mtime, 'text' => FILE_MTIME);
        $column2->set_sort_column_id(FILE_MTIME);
        $treeview->append_column($column2);
    }

These are standard operations to construct a TreeView:
  1. create a TreeModel object using ListStore or TreeStore with several
     data types. Note that the columns of TreeModel may or may not
     appear in TreeViewColumn.
  2. Create a TreeView using the TreeModel
  3. Create one ore more TreeViewColumn and append them to the
     TreeView. For each TreeViewColumn, you can add one or more
     CellRender to it. Each CellRender can set attribute according
     to any column of TreeModel or set by a function.

The third one also can be done by `insert_column_with_attributes' or
`insert_column_with_data_func' if the column has only one cell. For
example, to insert the second column, it can be rewrite as:

     ## Add second column
     $treeview->insert_column_with_attributes(
         1, "modify time",
         Gtk2::CellRendererText->new(),
         'text' => FILE_MTIME
     );
     $treeview->get_column(1)->set_sort_column_id(FILE_MTIME);

To sort the column by mtime, the TreeModel have to change, The
FILE_MTIME should keep the mtime of the file as integer not the text.
To show the date more readable, I use Date::Format::strftime to show
the mtime:

    sub create_model {
        my $dir = shift;
        my $model = Gtk2::ListStore->new(
            'Glib::String', 'Glib::String', 'Glib::Int'
        );
        opendir(DIR, $dir) or die "Can't open directory $dir: $!";
        foreach my $file ( readdir(DIR) ) {
            my $iter = $model->append();
            $model->set(
                $iter,
                FILE_ICON, ( -d "$dir/$file" ? 'gtk-directory' : 'gtk-file'  
),
                FILE_NAME, $file,
                FILE_MTIME, (stat("$dir/$file"))[9]
            );
        }
        return $model;
    }

    sub add_columns {
        use Date::Format;
        my $treeview = shift;
        ## Add first column
        my $column1 = Gtk2::TreeViewColumn->new();
        $column1->set_title('file name');
        ### first cell for column one
        my $icon = Gtk2::CellRendererPixbuf->new();
        $column1->pack_start($icon, FALSE);
        $column1->set_attributes( $icon, 'stock-id' => FILE_ICON );
        ### second cell for column one
        my $name = Gtk2::CellRendererText->new();
        $column1->pack_start($name, FALSE);
        $column1->set_attributes( $name, 'text' => FILE_NAME );
        $column1->set_sort_column_id(FILE_NAME);
        $treeview->append_column($column1);
        ## Add second column
        $treeview->insert_column_with_data_func(
            1, "modify time",
            Gtk2::CellRendererText->new(),
            sub {
                my ($tree_column, $cell, $model, $iter) = @_;
                my ($mtime) = $model->get ($iter, FILE_MTIME);
                my @lc = localtime($mtime);
                $cell->set (text => strftime("%c", @lc));
            }
        );
        $treeview->get_column(1)->set_sort_column_id(FILE_MTIME);
    }

If we want display the contents of subdirectory as well, it is better
to use TreeStore instead of ListStore. What we have to do, it to
change the `create_model' functon:

    sub read_dir {
        my $model = shift;
        my $dir = shift;
        my $iter = shift;
        opendir(DIR, $dir) or die "Can't open directory $dir: $!";
        foreach my $file ( readdir(DIR) ) {
            next if $file =~ /^[.]/;
            my $full = "$dir/$file";
            my $iter_child = $model->append($iter);
            if ( -d $full ) {
                $model->set(
                    $iter_child,
                    FILE_ICON, 'gtk-directory',
                    FILE_NAME, $file,
                    FILE_MTIME, (stat("$dir/$file"))[9]
                );
                read_dir($model, $full, $iter_child);
            } else {
                $model->set(
                    $iter_child,
                    FILE_ICON, 'gtk-file',
                    FILE_NAME, $file,
                    FILE_MTIME, (stat("$dir/$file"))[9]
                );
            }
        }
    }

    sub create_model {
        my $dir = shift;
        my $model = Gtk2::TreeStore->new(
            'Glib::String', 'Glib::String', 'Glib::Int'
        );
        read_dir($model, $dir);
        return $model;
    }

The contents of the directory will read into memory at start up. This
may take quit a lot time if the directory contains many files. It is
better to expand the directory when we want to. So the signal
row-expanded is what we need. First we need add a column to TreeModel
so that we can easy get the full file name of the TreeIter.

    use constant {
        FILE_ICON  => 0,
        FILE_NAME  => 1,
        FILE_MTIME => 2,
    };

The TreeModel is very simple now:

    sub create_model {
        my $dir = shift;
        my $model = Gtk2::TreeStore->new(
            'Glib::String', 'Glib::String', 'Glib::String', 'Glib::Int'
        );
        return $model;
    }

Then we connect the signal and open the $dir in function `ret_vbox':

     $treeview->signal_connect( 'row-expanded' => \&expand_dir );
     open_dir($model, $dir, undef);

Add two new function:

    sub expand_dir {
        my ($treeview,$iter,$path,$self) = @_;
        my $model = $treeview->get_model();
        my $dir = $model->get($iter, FILE_FULE_NAME);
        my $first = $model->iter_nth_child($iter, 0);
        unless ( defined $model->get($first, FILE_NAME) ) {
            open_dir($model, $dir, $iter);
            $model->remove($first);
        }
        return FALSE;
    }

    sub open_dir {
        my ($model, $dir, $parent_iter) = @_;
        opendir(DIR, $dir) or die "Can't open directory $dir: $!";
        foreach my $file ( readdir(DIR) ) {
            next if $file =~ /^[.]/;
            my $full = "$dir/$file";
            my $iter = $model->append($parent_iter);
            $model->set(
                $iter,
                FILE_ICON, ( -d $full ? 'gtk-directory' : 'gtk-file' ),
                FILE_NAME, $file,
                FILE_FULE_NAME, $full,
                FILE_MTIME, (stat($full))[9]
            );
            if ( -d $full ) {
                my $dummy = $model->append($iter);
            }
        }
        my $dummy = $model->iter_nth_child($parent_iter, 0);
        unless ( $model->get($dummy, FILE_NAME) ) {
            $model->remove($dummy);
        }
        return $model;
    }

If you want the file list looks better, you may want sort the contents
by name but the directory always list first. What we need to do is to
add an sort_func to the TreeModel. Add the following lines to the
function `add_column':

     # Set customized sort funciton
     my $model = $treeview->get_model;
     $model->set_sort_func(
         FILE_NAME,
         sub {
             my ($model, $itera, $iterb) = @_;
             if ( $model->get($itera, FILE_NAME)
               && $model->get($iterb, FILE_NAME)
               && $model->get($itera, FILE_ICON)
               && $model->get($iterb, FILE_ICON)
                  ) {
       $model->get($itera, FILE_ICON) cmp $model->get($iterb, FILE_ICON)
    || $model->get($itera, FILE_NAME) cmp $model->get($iterb, FILE_NAME);
             }
         });
     $model->set_sort_column_id(FILE_NAME, $column1->get_sort_order);
     $model->sort_column_changed();

Ok, this is the final version(Encode module is used to support file
name with multibyte characters):

use Gtk2 '-init';
use Glib qw(TRUE FALSE);
use Encode qw/encode decode/;
use constant {
     FILE_ICON  => 0,
     FILE_NAME  => 1,
     FILE_FULE_NAME => 2,
     FILE_MTIME => 3,
};

my $window = Gtk2::Window->new('toplevel');
$window->signal_connect('delete_event' => sub { Gtk2->main_quit; });
ret_vbox($window);
$window->show();
Gtk2->main;

sub ret_vbox {
     my $top = shift;
     my $dir = '.';
     $top->set_title('Directory contents of '. $dir);
     my $vbox = Gtk2::VBox->new(FALSE, 4);
     my $sw = Gtk2::ScrolledWindow->new;
     $sw->set_size_request (600, 400);
     $sw->set_policy ('automatic', 'automatic');
     # create TreeView
     my $model = create_model($dir);
     my $treeview = Gtk2::TreeView->new_with_model($model);
     open_dir($model, $dir, undef);
     add_columns($treeview);
     $treeview->signal_connect( 'row-expanded' => \&expand_dir );
     $sw->add($treeview);
     $vbox->pack_start($sw,TRUE,TRUE,0);
     $vbox->show_all();
     $top->add($vbox);
}

sub create_model {
     my $dir = shift;
     my $model = Gtk2::TreeStore->new(
         'Glib::String', 'Glib::String', 'Glib::String', 'Glib::Int'
     );
     return $model;
}

sub expand_dir {
     my ($treeview,$iter,$path,$self) = @_;
     my $model = $treeview->get_model();
     my $dir = $model->get($iter, FILE_FULE_NAME);
     my $first = $model->iter_nth_child($iter, 0);
     unless ( defined $model->get($first, FILE_NAME) ) {
         open_dir($model, $dir, $iter);
         $model->remove($first);
     }
     return FALSE;
}

sub open_dir {
     my ($model, $dir, $parent_iter) = @_;
     opendir(DIR, $dir) or die "Can't open directory $dir: $!";
     foreach my $file ( readdir(DIR) ) {
         next if $file =~ /^[.]/;
         my $uni_name = decode('utf8', $file);
         my $full = "$dir/$uni_name";
         my $iter = $model->append($parent_iter);
         $model->set(
             $iter,
             FILE_ICON, ( -d $full ? 'gtk-directory' : 'gtk-file' ),
             FILE_NAME, $uni_name,
             FILE_FULE_NAME, $full,
             FILE_MTIME, (stat($full))[9]
         );
         if ( -d $full ) {
             my $dummy = $model->append($iter);
         }
     }
}

sub add_columns {
     use Date::Format;
     my $treeview = shift;
     my $column;
     ## Add first column
     my $column1 = Gtk2::TreeViewColumn->new();
     $column1->set_title('file name');
     ### first cell for column one
     my $icon = Gtk2::CellRendererPixbuf->new();
     $column1->pack_start($icon, FALSE);
     $column1->set_attributes( $icon, 'stock-id' => FILE_ICON );
     ### second cell for column one
     my $name = Gtk2::CellRendererText->new();
     $column1->pack_start($name, FALSE);
     $column1->set_attributes( $name, 'text' => FILE_NAME );
     $column1->set_sort_column_id(FILE_NAME);
     # Set customized sort funciton
     my $model = $treeview->get_model;
     $model->set_sort_func(
         FILE_NAME,
         sub {
             my ($model, $itera, $iterb) = @_;
             if ( $model->get($itera, FILE_NAME)
               && $model->get($iterb, FILE_NAME)
               && $model->get($itera, FILE_ICON)
               && $model->get($iterb, FILE_ICON) ) {
                 $model->get($itera, FILE_ICON) cmp $model->get($iterb,  
FILE_ICON)
              || $model->get($itera, FILE_NAME) cmp $model->get($iterb,  
FILE_NAME);
             }
         });
     $model->set_sort_column_id(FILE_NAME, $column1->get_sort_order);
     $model->sort_column_changed();
     $treeview->append_column($column1);
     ## Add second column
     $treeview->insert_column_with_data_func(
         1, "modify time",
         Gtk2::CellRendererText->new(),
         sub {
             my ($tree_column, $cell, $model, $iter) = @_;
             my ($mtime) = $model->get ($iter, FILE_MTIME);
             my @lc = localtime($mtime);
             $cell->set (text => strftime("%c", @lc));
         }
     );
     $treeview->get_column(1)->set_sort_column_id(FILE_MTIME);
}

On Mon, 30 Jul 2007 22:18:30 +0800, muppet <[EMAIL PROTECTED]> wrote:

> Well, "ugly" is subjective, so what you're saying is unclear.  Are you  
> worried
> that the icon will not indent with child nodes?  Have you *tried* it?
Sorry for my poor english. I know it is my fault, not gtk-perl. In fact,  
gtk-perl
is powerful, and interesting, and I spent all the days to learn it.

-- 
Best regards,
Ye Wenbin
_______________________________________________
gtk-perl-list mailing list
gtk-perl-list@gnome.org
http://mail.gnome.org/mailman/listinfo/gtk-perl-list

Reply via email to