jmudspot has asked for the wisdom of the Perl Monks concerning the following question:
I've been trying to write a simple GTK-perl treeview.
The code in Dirk van der Walt's 'Study Guide' provides a very nice, but long, example (posted below). It has a treeview with five rows. In the rows are the strings
Gtk2::Gdk::PixbufYou can click on any of the cells to get some nice information about a Perl Package.
It's pretty simple to adapt the code to put different data in the rows. My problem is that the data in my treeview is not unique. For example, it might look like this:
redIt's not enough to know that the user clicked on 'green', I need to know *which* green they clicked on. Ideally, I would like some kind of data structure which maps the data in the treeview, onto some other data. Perhaps...
red => 'Red #1'...or, in more practical terms, maps it onto the blessed reference of some Perl object...
red => ColourDefn=HASH(0x3e60c38)I've tried every solution I can think of, having read the POD and guides written by van der Walt (any others), and gotten precisely nowhere. Tried multiple columns, invisible columns, Gtk2::TreeRowReference, etc etc.
I'm hoping someone can point me in the right direction, else I fear I'll still be stuck on this function when Christmas comes...
#!/usr/bin/perl -w use Carp; use strict; use warnings; use constant TRUE => 1; use constant FALSE => !TRUE; use Gtk2 -init; use Gtk2::SimpleList; my $have_podviewer = eval "use Gtk2::PodViewer; 1"; use Data::Dumper; our @VERSION = 0.006; our @namespaces = qw(Gtk2 Gtk2::Gdk Gtk2::Gdk::Event); our %typemap = ( 'Glib::Boolean' => 'boolean', 'Glib::String' => 'string', 'Glib::Int' => 'integer', 'Glib::Uint' => 'unsigned', 'Glib::Float' => 'float', 'Glib::Double' => 'double', ); misc_init (); my @actions = ( [ 'gtk-quit', "Quit", sub {Gtk2->main_quit} ], [ 'namespaces', "Edit the list of object namespaces", \&namespaces ] +, [ 'gtk-help', "Help!", \&manual ], [ 'gtk-dialog-info', "About this program", \&about ], ); my $window = Gtk2::Window->new; $window->signal_connect (delete_event => sub {Gtk2->main_quit}); $window->set_title ('Object Browser'); my $vbox = Gtk2::VBox->new (FALSE, 0); $window->add ($vbox); my $toolbar = Gtk2::Toolbar->new; $vbox->pack_start ($toolbar, FALSE, FALSE, 0); for (my $a = 0 ; $a < @actions ; $a++) { $toolbar->insert_stock ($actions[$a][0], $actions[$a][1], '', $actions[$a][2], undef, $a); } $toolbar->append_widget (Gtk2::VSeparator->new, '', ''); my $search_entry = Gtk2::Entry->new; $toolbar->append_widget ($search_entry, 'Jump to a specific object by +name', ''); my $find = Gtk2::Button->new ('_Find'); $toolbar->append_widget ($find, 'Jump to a specific object by name', ' +'); $search_entry->signal_connect (activate => sub {$find->clicked}); $find->signal_connect (clicked => \&do_search); $find->set_sensitive (FALSE); # don't let the user type spaces in this entry. $search_entry->signal_connect (insert_text => sub { my (undef, $string, undef, $position) = @_; $string =~ s/^\s*//; $string =~ s/\s*//; ($string, $position) }); # the button's sensitivity depends on the contents of the search entry +. $search_entry->signal_connect (changed => sub { $find->set_sensitive ($search_entry->get_text); }); $window->set_focus ($search_entry); my $hpaned = Gtk2::HPaned->new; $vbox->pack_end ($hpaned, TRUE, TRUE, 0); $hpaned->set_position (200); my $object_model = Gtk2::TreeStore->new ('Glib::String'); my $object_tree = Gtk2::TreeView->new ($object_model); $object_tree->append_column (Gtk2::TreeViewColumn->new_with_attributes ("Class", Gtk2::CellRendererText->new, text => 0)); my $scroller = Gtk2::ScrolledWindow->new; $scroller->add ($object_tree); $scroller->set_policy (qw/automatic automatic/); $hpaned->add1 ($scroller); my $notebook = Gtk2::Notebook->new; $hpaned->add2 ($notebook); my $prop_tree = PropertyView->new; $scroller = Gtk2::ScrolledWindow->new; $scroller->add ($prop_tree); $scroller->set_policy (qw/automatic automatic/); my $label = Gtk2::Label->new_with_mnemonic ('P_roperties'); $notebook->append_page ($scroller, $label); my $sig_tree = SignalView->new; $scroller = Gtk2::ScrolledWindow->new; $scroller->add ($sig_tree); $scroller->set_policy (qw/automatic automatic/); $label = Gtk2::Label->new_with_mnemonic ('_Signals'); $notebook->append_page ($scroller, $label); my $podviewer; if ($have_podviewer) { $podviewer = Gtk2::PodViewer->new; # $podviewer->signal_connect (link_clicked => sub { # my (undef, $text) = @_; # warn "link clicked"; # }); $scroller = Gtk2::ScrolledWindow->new; $scroller->set_policy (qw/automatic automatic/); $scroller->add ($podviewer); $label = Gtk2::Label->new_with_mnemonic ('_POD'); $notebook->append_page ($scroller, $label); } fill_tree ($object_tree); $object_tree->signal_connect (row_activated => sub { my ($tree, $path, $column) = @_; if ($tree->row_expanded ($path)) { $tree->collapse_row ($path); } else { $tree->expand_row ($path, FALSE); } }); $object_tree->get_selection->set_mode ('browse'); $object_tree->get_selection->signal_connect (changed => sub { my $selection = shift; my ($model, $iter) = $selection->get_selected; return unless $iter; my $type = $model->get ($iter, 0); $prop_tree->set_type ($type); $sig_tree->set_type ($type); if ($podviewer) { Glib::Source->remove ($podviewer->{timeout}) if $podviewer->{timeout}; $podviewer->{timeout} = Glib::Timeout->add (1000, sub { $podviewer->load ($type); undef $podviewer->{timeout}; FALSE; }); } }); $window->set_default_size (700, 450); $window->show_all; Gtk2->main; sub about { my $name = "Object Browser"; my $description = "An object browser for Gtk2-Perl developers"; my $copyright = "(c) 2004 by muppet <scott at asofyet dot org>"; my $dlg; if ($Gtk2::VERSION < 1.031) { $dlg = Gtk2::MessageDialog->new ($window, [], 'info', 'close', "$name\n\n" ."$description.\n\n" ."$copyright"); } else { $copyright =~ s/</\</g; $copyright =~ s/>/\>/g; $dlg = Gtk2::MessageDialog->new ($window, [], 'info', 'close', undef); $dlg->set_markup ("<big><b>$name</b></big>\n\n" ."$description.\n\n" ."$copyright"); } $dlg->signal_connect (response => sub {$_[0]->destroy}); $dlg->show; } sub connect_proxy { my ($uimanager, $action, $proxy, $statusbar) = @_; if ($proxy->isa (Gtk2::MenuItem::)) { # we know from the design of this particular program that # we'll only set up these things once, so we can just use # normal closures. if the objects change on the fly, this # construction could be problematic. $proxy->signal_connect (select => sub { $statusbar->push (0, $action->get ('tooltip') || '') }); $proxy->signal_connect (deselect => sub { $statusbar->pop (0) }); } } sub fill_tree { my $tree = shift; my $model = $tree->get_model; # search through the symbol table for things which inherit # from Glib::Object; then turn this information into a tree # representing the object hierarchy. this will be messy. # # GObject supports only single inheritance, so we only need to # find a single parent inheriting from Glib::Object; the rest # of the things in @ISA will be GInterfaces or perl classes. my %forward = (); foreach my $pkg (@namespaces) { no strict; my @keys = keys %{ $pkg."::" }; if (0 == @keys) { eval "use $pkg; 1"; next if $@; @keys = keys %{ $pkg."::" }; } foreach my $k (@keys) { $k =~ s/^(.*)::$/$pkg\::$1/; no strict; if (UNIVERSAL::isa $k, Glib::Object::) { foreach my $p (@{$k."::ISA"}) { if (UNIVERSAL::isa $p, Glib::Object) { push @{ $forward{$p} }, $k; # GObject supports only single # inheritance - "there can be # only one", and this is it. last; } } } } } $model->clear; # Glib::Object is our starting point... my $iter = $model->append (undef); $model->set ($iter, 0, 'Glib::Object'); # add a dummy child, so the row may be expanded. $model->append ($iter); $model->{forward} = \%forward; # load the rest on demand. $tree->signal_connect (row_expanded => sub { my ($treeview, $iter, $path) = @_; add_children ($treeview->get_model, $iter); }); $tree->expand_row ($model->get_path ($iter), FALSE); } sub add_children { my ($model, $parent) = @_; my $class = $model->get ($parent, 0); if (exists $model->{forward}{$class} && $model->iter_n_children ($parent) == 1 && !$model->get ($model->iter_nth_child ($parent, 0))) { my $thisset = $model->{forward}{$class}; delete $model->{forward}{$class}; foreach my $child (sort @$thisset) { # add this child. my $iter = $model->append ($parent); $model->set ($iter, 0, $child); # set things up to this one may expand if it has # children. $model->append ($iter) if exists $model->{forward}{$child}; } # remove the dummy child. $model->remove ($model->iter_nth_child ($parent, 0)); } } sub do_search { my $type = $search_entry->get_text; return unless $type; eval { my @ancestors = reverse Glib::Type->list_ancestors ($type); my @indices = (); my $path = Gtk2::TreePath->new; my $model = $object_tree->get_model; my $iter = undef; ANCESTOR: foreach (@ancestors) { my $n = $model->iter_n_children ($iter); foreach my $i (0..$n-1) { my $child = $model->iter_nth_child ($iter, $i); if ($_ eq ($model->get ($child, 0))[0]) { $iter = $child; $path->append_index ($i); # force lazy-loading $object_tree->expand_to_path ($path); next ANCESTOR; } } croak "Can't find ancestor $_ of $type"; } $object_tree->get_selection->select_path ($path); $object_tree->scroll_to_cell ($path); }; if ($@) { $@ =~ s/ at .* line \d+// if $@ =~ /not registered with/; error ($@); }; } sub error { my $dlg = Gtk2::MessageDialog->new ($window, [], 'error', 'ok', $_ +[0]); $dlg->run; $dlg->destroy; } sub namespaces { if ($window->{namespaces_window}) { $window->{namespaces_window}->present; return; } my $dialog = Gtk2::Dialog->new ('Namespaces', $window, 'destroy-with-parent', 'gtk-apply' => 'accept', 'gtk-close' => 'close'); my $hbox = Gtk2::HBox->new (FALSE, 6); $hbox->set_border_width (6); $dialog->vbox->add ($hbox); my $namespaces = Gtk2::SimpleList->new ('' => 'text'); $namespaces->set_headers_visible (FALSE); $namespaces->set_column_editable (0, TRUE); @{ $namespaces->{data} } = @namespaces; my $scroller = Gtk2::ScrolledWindow->new; $scroller->set_policy ('never', 'automatic'); $scroller->add ($namespaces); $hbox->add ($scroller); my $vbox = Gtk2::VBox->new (FALSE, 6); $hbox->pack_start ($vbox, FALSE, FALSE, 0); my $add = Gtk2::Button->new_from_stock ('gtk-add'); $vbox->pack_start ($add, FALSE, FALSE, 0); $add->signal_connect (clicked => sub { my $model = $namespaces->get_model; my $path = $model->get_path ($model->append); $namespaces->scroll_to_cell ($path, undef, TRUE, 1.0, 0.0); # let the scrolling finish before we set the cell editable. Gtk2->main_iteration while Gtk2->events_pending; $namespaces->set_cursor ($path, $namespaces->get_column (0), TRUE); }); my $remove = Gtk2::Button->new_from_stock ('gtk-remove'); $vbox->pack_start ($remove, FALSE, FALSE, 0); $remove->set_sensitive (FALSE); $namespaces->get_selection->signal_connect (changed => sub { $remove->set_sensitive ($_[0]->count_selected_rows) }); $remove->signal_connect (clicked => sub { if ($Gtk2::VERSION < 1.030) { # splice isn't fully implemented on SimpleList in this # version of Gtk2. we'll have to remove the selected # row by hand. my $path = $namespaces->get_selection->get_selected_rows; my $model = $namespaces->get_model; $model->remove ($model->get_iter ($path)); } else { my ($sel) = $namespaces->get_selected_indices; print "selected $sel\n"; splice @{ $namespaces->{data} }, $sel, 1; } }); $dialog->signal_connect (delete_event => sub { $dialog->response ('delete-event'); return TRUE; }); $dialog->signal_connect (response => sub { my (undef, $response) = @_; if ($response eq 'accept') { @namespaces = grep { length } # ignore blanks map { $_->[0] } @{$namespaces->{data}}; fill_tree ($object_tree); } else { $dialog->hide; } }); $dialog->show_all; $window->{namespaces_window} = $dialog; } my @namespaces_xpm; BEGIN { @namespaces_xpm = ( '48 48 7 1', ' c None', '. c #0C0707', '+ c #FEFEFE', '@ c #D31B1B', '# c #F0F0F0', '$ c #AE5B5B', '% c #D9D9D9', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' @@@@@@ @@@@@@ ', ' @@@@@@@ @@@@@@ ', ' @@@@@@@@ @@@@@@ ', ' @@@@@@@@ @@@@@@ ', ' @@@@@@@@@ @@@@@@ ', ' @@@@@@@@@@ @@@@@@ ', ' @@@@@@@@@@@ @@@@@@ ', ' @@@@@@@@@@@ @@@@@@ ', ' @@@@@@@@@@@@ @@@@@@. ', ' @@@@@@@@@@@@@...@@@@@....... ', ' @@@@@@@@@@@@@....@@@@@......... ', ' @@@@@@@@@@@@@@..@@@@@............ ', ' @@@@@ @@@@@@@@@.@@@@@@............ ', ' @@@@@ @@@@@@@@..@@@@............... ', ' @@@@@ @@@@@@@@@@@@@@%$$........... ', ' @@@@@ .@@@@@@@@@@@@@$##%%........... ', ' @@@@@ ..@@@@@@@@@@@@$###+%........... ', ' @@@@@ ....@@@@@@@@@@@$+++++$.......... ', ' @@@@@......@@@@@@@@@@$++++++.......... ', ' @@@@@......@@@@@@@@@@$++++++%......... ', ' @@@@@......@@@@@@@@@@$+++++#%......... ', ' @@@@@......$%@@@@@@@@$+++++++$........ ', ' @@@@@......$#$@@@@@@@$++++++#$........ ', ' @@@@@......$#$@@@@@@@$+++++++%........ ', ' @@@@@.......++$@@@@@@$+++++#%%........ ', ' @@@@@.......#++%@$$$$%#+++++#$........ ', ' ........$+#++++##+++++++#$....... ', ' .........%++++++++++++++%........ ', ' ..........%++++++++++++%%....... ', ' ...........%%+++++++++#$........ ', ' ............$%%%%#%%%$........ ', ' .............................. ', ' ............................ ', ' ........................... ', ' ......................... ', ' ....................... ', ' ..................... ', ' .................. ', ' .............. ', ' ..... ', ' ', ); } sub misc_init { Gtk2::Stock->add ({ stock_id => 'namespaces', label => '_Namespaces', }); my $pixbuf = Gtk2::Gdk::Pixbuf->new_from_xpm_data (@namespaces_xpm +); my $icon_set = Gtk2::IconSet->new_from_pixbuf ($pixbuf); my $icon_factory = Gtk2::IconFactory->new; $icon_factory->add ('namespaces', $icon_set); $icon_factory->add_default; } sub manual { #use File::Spec; my $dlg = Gtk2::Dialog->new ('Help', $window, 'destroy-with-parent +', 'gtk-close' => 'close'); $dlg->signal_connect (response => sub {$dlg->destroy}); $dlg->set_default_size (400, 300); my $manual; if ($have_podviewer) { $manual = Gtk2::PodViewer->new; $manual->load ($0); } else { $manual = Gtk2::TextView->new; my $buffer = $manual->get_buffer; my $text = `perldoc $0`; $buffer->insert ($buffer->get_start_iter, $text); $dlg->vbox->pack_start (Gtk2::Label->new ("You don't have Gtk2::PodViewer installed;" ." falling back to plain text.\nThis could" ." be very ugly and hard to read.\nPlease" ." consider getting Gtk2::PodViewer from" ." CPAN."), FALSE, FALSE, 10); } my $scroller = Gtk2::ScrolledWindow->new; $scroller->set_policy ('automatic', 'automatic'); $scroller->set_shadow_type ('in'); $scroller->add ($manual); $dlg->vbox->add ($scroller); $dlg->show_all; } #===================================================================== +====== package PropertyView; use strict; use constant TRUE => 1; use Gtk2; BEGIN { our @ISA = qw(Gtk2::TreeView); } sub new { my $class = shift; my $model = Gtk2::ListStore->new ('Glib::String', # name 'Glib::String', # type 'Glib::String', # flags 'Glib::String', # descr ); my $self = Gtk2::TreeView->new ($model); $self->set_rules_hint (TRUE); $self->get_selection->set_mode ('none'); foreach ([Name => 0], [Type => 1], [Flags => 2], [Description => 3]) { my $col = Gtk2::TreeViewColumn->new_with_attributes ($_->[0], Gtk2::CellRendererText->new, text => $_->[1]); $col->set_sizing ('autosize'); $self->append_column ($col); } return bless $self, $class; } sub set_type { my ($propview, $typename) = @_; return if $propview->{type} and $propview->{type} eq $typename; $propview->{type} = $typename; my $model = $propview->get_model; $model->clear; eval { foreach my $p (sort { $a->{name} cmp $b->{name} } grep { $_->{owner_type} eq $typename } $typename->list_properties) { my $iter = $model->append; my $flagsstr = ''; $flagsstr .= 'R' if $p->{flags} >= 'readable'; $flagsstr .= 'W' if $p->{flags} >= 'writable'; $flagsstr .= 'c' if $p->{flags} >= 'construct'; $flagsstr .= 'C' if $p->{flags} >= 'construct-only'; $flagsstr .= 'P' if $p->{flags} >= 'private'; $model->set ($iter, 0, $p->{name}, 1, $main::typemap{$p->{type}} || $p->{type}, 2, $flagsstr, 3, $p->{descr} || ''); } }; # if the class is not a GType, but just inherits perl-wise (e.g. # SimpleList), then the bindings will warn "package ... is not # registered with GPerl". we might want to do something spiffy # like change it to gray or italics or something. warn $@ if $@; } #===================================================================== +====== package SignalView; use strict; use constant TRUE => 1; use Gtk2; BEGIN { our @ISA = qw(Gtk2::TreeView); } sub new { my $class = shift; my $model = Gtk2::ListStore->new ('Glib::String', # name 'Glib::String', # type 'Glib::String', # flags 'Glib::String', # descr ); my $self = Gtk2::TreeView->new ($model); $self->set_rules_hint (TRUE); $self->get_selection->set_mode ('none'); foreach (['Return Type' => 0], [Name => 1], ['Param Types' => 2], [Flags => 3]) { my $col = Gtk2::TreeViewColumn->new_with_attributes ($_->[0], Gtk2::CellRendererText->new, text => $_->[1]); $col->set_sizing ('autosize'); $self->append_column ($col); } return bless $self, $class; } sub set_type { my ($sigview, $typename) = @_; return if $sigview->{type} and $sigview->{type} eq $typename; $sigview->{type} = $typename; my $model = $sigview->get_model; $model->clear; eval { foreach my $s (sort { $a->{signal_name} cmp $b->{signal_name} +} grep { $_->{itype} eq $typename } Glib::Type->list_signals ($typename)) { my $iter = $model->append; my $ret = $s->{return_type} ? $main::typemap{$s->{return_type}} || $s->{return_type} : ''; my $params = $s->{param_types} ? join ', ', map { $main::typemap{$_} || $_ } @{$s->{param_types}} : ''; $model->set ($iter, 0, $ret, 1, $s->{signal_name}, 2, $params, 3, "@{$s->{signal_flags}}"); } }; # if the class is not a GType, but just inherits perl-wise (e.g. # SimpleList), then the bindings will warn "package ... is not # registered with GPerl". we might want to do something spiffy # like change it to gray or italics or something. warn $@ if $@; } package main; __END__ =head1 NAME object_browser =head1 SYNOPSIS object_browser =head1 DESCRIPTION This gtk2-perl utility displays information about Glib::Objects. The code actually scrapes through the Perl symbol table for packages t +hat derive from Glib::Object, and then queries the GLib type system for in +formation about those objects. If you have Gavin Brown's excellent L<Gtk2::PodViewer> installed, you +can also look at the POD for those objects. The user interface is in two main parts; an object hierarchy on the le +ft, and a notebook full of information panels on the right. The panels on + the right display information about the currently selected node in the tre +e on the left. (Fairly standard stuff.) =head2 WHERE ARE MY OBJECTS? As mentioned above, the program scrapes the Perl symbol table for pack +ages derived from Glib::Object. By default, the program scrapes only three namespaces, Gtk2, Gtk2::Gdk, and Gtk2::Gdk::Event. You can easily add + other namespaces at runtime by clicking on the "Namespaces" button and editi +ng the list. (Hint: double-click a row to edit it.) If a package named in the list contains no symbols, the program does a +n eval "use $pkg; 1"; to load it, and then tries again. Any objects found in that namespace + will be inserted into the proper places in the hierarchy (so you may not se +e them immediately). This way you can look at Gnome2, Gnome2::Canvas, etc, without having t +o edit the code of the object browser itself. :-) =head1 BUGS This is a work in progress; many features are missing, including the s +piffy action-based menus which require gtk+ 2.4.0. No support for GInterfaces. Don't really know what to do about this. No support for listing enum and flag values. This is possible, but co +llecting enum values will not be easy as they don't get registered in the same +way as flags types. =head1 AUTHOR muppet <scott at asofyet dot org>, with inspiration from the examples and tests distributed with gtk+. =head1 SEE ALSO L<Glib>, L<Gtk2>, L<http://gtk2-perl.sourceforge.net/>. Gavin Brown's L<Gtk2::PodViewer> will be used if available. =head1 COPYRIGHT and LICENSE (c) 2004 by muppet <scott at asofyet dot org>. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut __DATA__ GInterface Gtk2::TreeModel Gtk2::TreeDragSource Gtk2::TreeDragDest Gtk2::TreeSortable Gtk2::Editable Gtk2::CellEditable Gtk2::FileChooser
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re: Gtk2::TreeView non-unique cell data
by zentara (Cardinal) on Aug 23, 2011 at 12:24 UTC |