#!/usr/bin/perl -w =doc Say you have some sophisticated sort algorithm you need to apply to a column of data in a tree, and the sort algorithm takes parameters that you can tweak. You'd like to allow the user to select those parameters from a context menu that pops up from the header button, that same button you click to change sort column and sort order. That's getting into some deep arcana, so here's a contrived example. -- muppet, 30 mar 04 =cut use strict; use Glib qw(TRUE FALSE); use Gtk2 -init; # # We'll start with a single-column model with some "interesting" data. # This data is three "virtual" columns that can be sorted. There are # plenty of ways we could store this that would be easier, but they'd # also make it pointless to do what we're about to do, so bear with me. # my $model = Gtk2::ListStore->new (qw(Glib::String)); for (my $i = 0; $i < 5 ; $i++) { for (my $j = 0; $j < 5 ; $j++) { for (my $k = 0; $k < 5 ; $k++) { $model->set ($model->append, 0, "$i, $j, $k"); } } } # # And a custom sort func that sorts on the virtual column. # Yes, this is very slow. Like i said, it's a contrived example. # sub foosort { my ($liststore, $itera, $iterb, $sortkey) = @_; my @a = split /,\s*/, $liststore->get ($itera, 0); my @b = split /,\s*/, $liststore->get ($iterb, 0); return $a[$sortkey] <=> $b[$sortkey]; } # Use different sort column ids for the different sort options. $model->set_sort_func (0, \&foosort, 0); $model->set_sort_func (1, \&foosort, 1); $model->set_sort_func (2, \&foosort, 2); # # Now put that model into a view... mostly boilerplate here. # my $treeview = Gtk2::TreeView->new ($model); $treeview->set_headers_visible (1); my $col = Gtk2::TreeViewColumn->new_with_attributes ('Fancy sortable column', Gtk2::CellRendererText->new, text => 0); $treeview->append_column ($col); # Select the initial sort id. $col->set_sort_column_id (0); # # Now, here comes the heavy wizardry. We want to allow the user to # right-click on the column heading to pop up a context menu. To do # this, we need to get a hold of the button that's used as the column # header. I've looked far and wide and have not found a sanctioned # "clean" way to get this button, but as mentioned here: # http://mail.gnome.org/archives/gtk-list/2003-November/msg00175.html # we can cheat by seeking up the widget hierarchy for it. # # We must start off by setting a custom widget into the column. # get_widget() will return undef unless we've previously called set_widget(), # so let's create a label we can use as the heading label. This is good, # anyway, because we'll want to mangle the text of the heading to show which # virtual column is selected for sorting. # my $label = Gtk2::Label->new ($col->get_title." (0)"); $col->set_widget ($label); $label->show; # very important, show_all doesn't get this widget! # # And now, we need to find the button. To avoid problems in the future # should the actual layout of widgets change, we'll seek instead of just # assuming it's always three layers deep. If you're really paranoid, # you can modify this to avoid the possibility of infinite looping, but # we know there's a button in the tree, so i won't muddy the code. # my $button = $col->get_widget; # not a button do { $button = $button->get_parent; } until ($button->isa ('Gtk2::Button')); $button->signal_connect (button_release_event => sub { my (undef, $event) = @_; # Do completely normal things unless this is a right-button click. return FALSE unless $event->button == 3; # otherwise, pop up a context menu that lets the user select the # virtual column on which to sort. my $menu = Gtk2::Menu->new; foreach (0, 1, 2) { my $item = Gtk2::MenuItem->new ($_); $menu->append ($item); $item->show; $item->signal_connect (activate => sub { my (undef, $sortkey) = @_; $col->set_sort_column_id ($sortkey); $model->set_sort_column_id ($sortkey, $col->get_sort_order); $model->sort_column_changed; # update the column heading to show which virtual # column is active. $label->set_text ($col->get_title." ($sortkey)"); }, $_); } $menu->popup (undef, undef, undef, undef, $event->button, $event->time); return TRUE; # stop propagation! }); # The rest is just boilerplate to get everything onscreen. my $scroller = Gtk2::ScrolledWindow->new; $scroller->set_policy ('never', 'always'); $scroller->add ($treeview); my $win = Gtk2::Window->new; $win->signal_connect (destroy => sub {Gtk2->main_quit}); $win->add ($scroller); $win->set_default_size (75, 150); $win->show_all; Gtk2->main;