1# ported from Tim-Phillip Mueller's Tree View tutorial, 2# http://scentric.net/tutorial/sec-custom-models.html 3# 4 5package CustomList; 6 7use Glib qw(TRUE FALSE); 8use Gtk2; 9use Carp; 10use Data::Dumper; 11use strict; 12use warnings; 13 14# maybe bad style, but makes life a lot easier 15use base Exporter::; 16 17our @EXPORT = qw/ 18 CUSTOM_LIST_COL_RECORD 19 CUSTOM_LIST_COL_NAME 20 CUSTOM_LIST_COL_YEAR_BORN 21 CUSTOM_LIST_N_COLUMNS 22/; 23 24# The data columns that we export via the tree model interface 25 26use constant { 27 CUSTOM_LIST_COL_RECORD => 0, 28 CUSTOM_LIST_COL_NAME => 1, 29 CUSTOM_LIST_COL_YEAR_BORN => 2, 30 CUSTOM_LIST_N_COLUMNS => 3, 31}; 32 33# 34# here we register our new type and its interfaces with the type system. 35# If you want to implement additional interfaces like GtkTreeSortable, 36# you will need to do it here. 37# 38 39use Glib::Object::Subclass 40 Glib::Object::, 41 interfaces => [ Gtk2::TreeModel:: ], 42 ; 43 44# 45# this is called everytime a new custom list object 46# instance is created (we do that in custom_list_new). 47# Initialise the list structure's fields here. 48# 49 50sub INIT_INSTANCE { 51 my $self = shift; 52 $self->{n_columns} = CUSTOM_LIST_N_COLUMNS; 53 $self->{column_types} = [ 54 'Glib::Scalar', # CUSTOM_LIST_COL_RECORD 55 'Glib::String', # CUSTOM_LIST_COL_NAME 56 'Glib::Uint', # CUSTOM_LIST_COL_YEAR_BORN 57 ]; 58 $self->{rows} = []; 59 60 # Random int to check whether an iter belongs to our model 61 $self->{stamp} = sprintf '%d', rand (1<<31); 62} 63 64 65# 66# this is called just before a custom list is 67# destroyed. Free dynamically allocated memory here. 68# 69 70sub FINALIZE_INSTANCE { 71 my $self = shift; 72 73 # free all records and free all memory used by the list 74 #warning IMPLEMENT 75} 76 77 78# 79# tells the rest of the world whether our tree model has any special 80# characteristics. In our case, we have a list model (instead of a tree). 81# Note that unlike the C version of this custom model, our iters do NOT 82# persist. 83# 84 85#sub GET_FLAGS { [qw/list-only iters-persist/] } 86sub GET_FLAGS { [qw/list-only/] } 87 88 89# 90# tells the rest of the world how many data 91# columns we export via the tree model interface 92# 93 94sub GET_N_COLUMNS { shift->{n_columns}; } 95 96 97# 98# tells the rest of the world which type of 99# data an exported model column contains 100# 101 102sub GET_COLUMN_TYPE { 103 my ($self, $index) = @_; 104 # and invalid index will send undef back to the calling XS layer, 105 # which will croak. 106 return $self->{column_types}[$index]; 107} 108 109 110# 111# converts a tree path (physical position) into a 112# tree iter structure (the content of the iter 113# fields will only be used internally by our model). 114# We simply store a pointer to our CustomRecord 115# structure that represents that row in the tree iter. 116# 117 118sub GET_ITER { 119 my ($self, $path) = @_; 120 121 die "no path" unless $path; 122 123 my @indices = $path->get_indices; 124 my $depth = $path->get_depth; 125 126 # we do not allow children 127 # depth 1 = top level; a list only has top level nodes and no children 128 die "depth != 1" unless $depth == 1; 129 130 my $n = $indices[0]; # the n-th top level row 131 132 return undef if $n >= @{$self->{rows}} || $n < 0; 133 134 my $record = $self->{rows}[$n]; 135 136 die "no record" unless $record; 137 die "bad record" unless $record->{pos} == $n; 138 139 # We simply store a pointer to our custom record in the iter 140 return [ $self->{stamp}, $n, $record, undef ]; 141} 142 143 144# 145# custom_list_get_path: converts a tree iter into a tree path (ie. the 146# physical position of that row in the list). 147# 148 149sub GET_PATH { 150 my ($self, $iter) = @_; 151 die "no iter" unless $iter; 152 153 my $record = $iter->[2]; 154 155 my $path = Gtk2::TreePath->new; 156 $path->append_index ($record->{pos}); 157 return $path; 158} 159 160 161# 162# custom_list_get_value: Returns a row's exported data columns 163# (_get_value is what gtk_tree_model_get uses) 164# 165 166sub GET_VALUE { 167 my ($self, $iter, $column) = @_; 168 169 die "bad iter" unless $iter; 170 171 return undef unless $column < @{$self->{column_types}}; 172 173 my $record = $iter->[2]; 174 175 return undef unless $record; 176 177 die "bad iter" if $record->{pos} >= @{$self->{rows}}; 178 179 if ($column == CUSTOM_LIST_COL_RECORD) { 180 return $record; 181 } elsif ($column == CUSTOM_LIST_COL_NAME) { 182 return $record->{name}; 183 } elsif ($column == CUSTOM_LIST_COL_YEAR_BORN) { 184 return $record->{year_born}; 185 } 186} 187 188 189# 190# iter_next: Takes an iter structure and sets it to point to the next row. 191# 192 193sub ITER_NEXT { 194 my ($self, $iter) = @_; 195 196 return undef 197 unless $iter && $iter->[2]; 198 199 my $record = $iter->[2]; 200 201 # Is this the last record in the list? 202 return undef 203 if $record->{pos} >= @{ $self->{rows} }; 204 205 my $nextrecord = $self->{rows}[$record->{pos} + 1]; 206 207 return undef unless $nextrecord; 208 die "invalid record" unless $nextrecord->{pos} == ($record->{pos} + 1); 209 210 return [ $self->{stamp}, $nextrecord->{pos}, $nextrecord, undef ]; 211} 212 213 214# 215# iter_children: Returns TRUE or FALSE depending on whether the row 216# specified by 'parent' has any children. If it has 217# children, then 'iter' is set to point to the first 218# child. Special case: if 'parent' is undef, then the 219# first top-level row should be returned if it exists. 220# 221 222sub ITER_CHILDREN { 223 my ($self, $parent) = @_; 224 225### return undef unless $parent and $parent->[1]; 226 227 # this is a list, nodes have no children 228 return undef if $parent; 229 230 # parent == NULL is a special case; we need to return the first top-level row 231 232 # No rows => no first row 233 return undef unless @{ $self->{rows} }; 234 235 # Set iter to first item in list 236 return [ $self->{stamp}, 0, $self->{rows}[0] ]; 237} 238 239 240# 241# iter_has_child: Returns TRUE or FALSE depending on whether 242# the row specified by 'iter' has any children. 243# We only have a list and thus no children. 244# 245 246sub ITER_HAS_CHILD { FALSE } 247 248# 249# iter_n_children: Returns the number of children the row specified by 250# 'iter' has. This is usually 0, as we only have a list 251# and thus do not have any children to any rows. 252# A special case is when 'iter' is undef, in which case 253# we need to return the number of top-level nodes, ie. 254# the number of rows in our list. 255# 256 257sub ITER_N_CHILDREN { 258 my ($self, $iter) = @_; 259 260 # special case: if iter == NULL, return number of top-level rows 261 return scalar @{$self->{rows}} 262 if ! $iter; 263 264 return 0; # otherwise, this is easy again for a list 265} 266 267 268# 269# iter_nth_child: If the row specified by 'parent' has any children, 270# set 'iter' to the n-th child and return TRUE if it 271# exists, otherwise FALSE. A special case is when 272# 'parent' is NULL, in which case we need to set 'iter' 273# to the n-th row if it exists. 274# 275 276sub ITER_NTH_CHILD { 277 my ($self, $parent, $n) = @_; 278 279 # a list has only top-level rows 280 return undef if $parent; 281 282 # special case: if parent == NULL, set iter to n-th top-level row 283 284 return undef if $n >= @{$self->{rows}}; 285 286 my $record = $self->{rows}[$n]; 287 288 die "no record" unless $record; 289 die "bad record" unless $record->{pos} == $n; 290 291 return [ $self->{stamp}, $n, $record ]; 292} 293 294 295# 296# iter_parent: Point 'iter' to the parent node of 'child'. As we have a 297# a list and thus no children and no parents of children, 298# we can just return FALSE. 299# 300 301sub ITER_PARENT { FALSE } 302 303# 304# ref_node and unref_node get called as the model manages the lifetimes 305# of nodes in the model. you normally don't need to do anything for these, 306# but may want to if you plan to implement data caching. 307# 308#sub REF_NODE { warn "REF_NODE @_\n"; } 309#sub UNREF_NODE { warn "UNREF_NODE @_\n"; } 310 311# 312# new: This is what you use in your own code to create a 313# new custom list tree model for you to use. 314# 315 316# we inherit new from Glib::Object::Subclass 317 318 319# 320# set: It's always nice to be able to update the data stored in a data 321# structure. So, here's a method to let you do that. We emit the 322# 'row-changed' signal to notify all who care that we've updated 323# something. 324# 325 326sub set { 327 my $self = shift; 328 my $treeiter = shift; 329 330 # create (col, value) pairs to update. 331 my %vals = @_; 332 333 # Convert the Gtk2::TreeIter to a more useable array reference. 334 # Note that the model's stamp must be passed in as an argument. 335 # This is so we can avoid trying to extract the guts of an iter 336 # that we did not create in the first place. 337 my $iter = $treeiter->to_arrayref($self->{stamp}); 338 339 my $record = $iter->[2]; 340 341 while (my ($col, $val) = each %vals) { 342 if ($col == CUSTOM_LIST_COL_NAME) { 343 $record->{name} = $val; 344 } elsif ($col == CUSTOM_LIST_COL_YEAR_BORN) { 345 $record->{year_born} = $val; 346 } elsif ($col == CUSTOM_LIST_COL_RECORD) { 347 warn "Can't update the value of the Record column!"; 348 } else { 349 warn "Invalid column used in set method!"; 350 } 351 } 352 353 $self->row_changed ($self->get_path ($treeiter), $treeiter); 354} 355 356# 357# get_iter_from_name: Sometimes, you have a bit of information that 358# uniquely identifies a record in your TreeModel, 359# but it doesn't convert easily to a TreePath, 360# so it's hard to get a TreeIter out of it. This 361# is an example of how to make a TreeModel that 362# can get iterators without having to find the path 363# first. 364# 365 366sub get_iter_from_name { 367 my $self = shift; 368 my $name = shift; 369 370 my ($record, $n); 371 372 for (0..scalar (@{$self->{rows}})) { 373 if ($self->{rows}[$_]->{name} eq $name) { 374 $record = $self->{rows}[$_]; 375 $n = $_; 376 last; 377 } 378 } 379 380 return Gtk2::TreeIter->new_from_arrayref([$self->{stamp}, $n, $record, undef]); 381} 382 383# 384# append_record: Empty lists are boring. This function can be used in your 385# own code to add rows to the list. Note how we emit the 386# "row-inserted" signal after we have appended the row 387# so the tree view and other interested objects know about 388# the new row. 389# 390 391sub append_record { 392 my ($self, $name, $year_born) = @_; 393 394 croak "usage: \$list->append_record (NAME, YEAR_BORN)" 395 unless $name; 396 397 my $newrecord = { 398 name => $name, 399# name_collate_key => g_utf8_collate_key(name,-1), # for fast sorting, used later 400 year_born => $year_born, 401 }; 402 403 push @{ $self->{rows} }, $newrecord; 404 $newrecord->{pos} = @{$self->{rows}} - 1; 405 406 # inform the tree view and other interested objects 407 # (e.g. tree row references) that we have inserted 408 # a new row, and where it was inserted 409 410 my $path = Gtk2::TreePath->new; 411 $path->append_index ($newrecord->{pos}); 412 $self->row_inserted ($path, $self->get_iter ($path)); 413} 414 415############################################################################ 416############################################################################ 417############################################################################ 418 419package main; 420 421no strict 'subs'; 422use Glib qw(TRUE FALSE); 423use Gtk2 -init; 424 425import CustomList; 426 427sub fill_model { 428 my $customlist = shift; 429 430 my @firstnames = qw(Joe Jane William Hannibal Timothy Gargamel); 431 my @surnames = qw(Grokowich Twitch Borheimer Bork); 432 433 foreach my $sname (@surnames) { 434 foreach my $fname (@firstnames) { 435 $customlist->append_record ("$fname $sname", 436 1900 + rand (103.0)) 437 } 438 } 439} 440 441sub create_view_and_model { 442 my $customlist = CustomList->new; 443 fill_model ($customlist); 444 445 my $view = Gtk2::TreeView->new ($customlist); 446 447 my $renderer = Gtk2::CellRendererText->new; 448 my $col = Gtk2::TreeViewColumn->new; 449 450 $col->pack_start ($renderer, TRUE); 451 $col->add_attribute ($renderer, text => &CustomList::CUSTOM_LIST_COL_NAME); 452 $col->set_title ("Name"); 453 $view->append_column ($col); 454 $renderer->set (editable => TRUE); 455 $renderer->signal_connect (edited => sub { 456 my ($cell, $pathstring, $newtext, $model) = @_; 457 my $path = Gtk2::TreePath->new_from_string ($pathstring); 458 my $iter = $model->get_iter ($path); 459 $model->set ($iter, &CustomList::CUSTOM_LIST_COL_NAME, $newtext); 460 }, $customlist); 461 462 $renderer = Gtk2::CellRendererText->new; 463 $col = Gtk2::TreeViewColumn->new; 464 $col->pack_start ($renderer, TRUE); 465 $col->add_attribute ($renderer, text => &CustomList::CUSTOM_LIST_COL_YEAR_BORN); 466 $col->set_title ("Year Born"); 467 $view->append_column ($col); 468 469 return $view; 470} 471 472{ 473 my $window = Gtk2::Window->new; 474 $window->set_default_size (200, 400); 475 $window->signal_connect (delete_event => sub {Gtk2->main_quit; 0}); 476 477 my $view = create_view_and_model(); 478 my $scrollwin = Gtk2::ScrolledWindow->new; 479 $scrollwin->add ($view); 480 $window->add ($scrollwin); 481 482 $window->show_all; 483 484 Gtk2->main; 485 486 exit 0; 487} 488 489 490############################################################################ 491############################################################################ 492############################################################################ 493