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