#!/usr/bin/perl -w package MupCellLayout; use strict; use warnings; use Gtk2; use Glib ':constants'; use Carp qw(croak); use Glib::Object::Subclass Gtk2::Widget::, signals => { size_request => \&do_size_request, size_allocate => \&do_size_allocate, expose_event => \&do_expose_event, }, properties => [ { pspec => Glib::ParamSpec->object ('model', 'Model', 'The data model', Gtk2::TreeModel::, ['readable', 'writable']), set => sub { my ($self, $model) = @_; delete $self->{displayed_row}; $self->{model} = $model; }, }, { pspec => Glib::ParamSpec->boxed ('displayed-row', 'Displayed Row', 'Row to display in model', Gtk2::TreePath::, ['readable', 'writable']), get => sub { my ($self) = @_; return $self->{displayed_row} ? $self->{displayed_row}->get_path () : undef; }, set => sub { my ($self, $path) = @_; croak "Trying to set the displayed row without a model!" unless $self->{model}; $self->{displayed_row} = Gtk2::TreeRowReference->new ($self->{model}, $path); $self->queue_resize (); $self->queue_draw (); }, }, Glib::ParamSpec->int ('spacing', 'Spacing', 'Pixels between packed renderers', 0, 20, 2, ['readable', 'writable']), ], interfaces => [ qw( Gtk2::CellLayout ) ], ; sub INIT_INSTANCE { my ($self) = @_; $self->no_window (TRUE); $self->{cells} = []; } sub FINALIZE_INSTANCE { my ($self) = @_; $self->clear (); delete $self->{model}; delete $self->{cells}; delete $self->{displayed_row}; } sub _set_cell_data { my $self = shift; my $path = $self->{displayed_row}->get_path; my $iter = $self->{model}->get_iter ($path); foreach my $info (@{ $self->{cells} }) { $info->{renderer}->freeze_notify (); foreach my $attr (@{ $info->{attributes} }) { my $val = $self->{model}->get ($iter, $attr->{column}); $info->{renderer}->set ($attr->{property}, $val); } $info->{renderer}->thaw_notify (); } } sub do_size_request { my ($self, $requisition) = @_; my $width = 0; my $height = 0; my $spacing = $self->get ('spacing'); $self->_set_cell_data () if $self->{displayed_row}; my $first_cell = TRUE; foreach my $info (@{ $self->{cells} }) { next unless $info->{renderer}->get ('visible'); $width += $spacing if $first_cell; my (undef, undef, $w, $h) = $info->{renderer}->get_size ($self, undef); $info->{requested_width} = $w; $width += $w; $height = $h if $h > $height; $first_cell = FALSE;; } $requisition->width ($width); $requisition->height ($height); } sub do_size_allocate { my ($self, $allocation) = @_; # XXX need a source hack to do this... #$self->allocation ($allocation); $self->allocation->x ($allocation->x); $self->allocation->y ($allocation->y); $self->allocation->width ($allocation->width); $self->allocation->height ($allocation->height); my $spacing = $self->get ('spacing'); my $expand_cell_count = 0; my $full_requested_width = 0; my $first_cell = TRUE; foreach my $info (@{ $self->{cells} }) { next unless $info->{renderer}->get ('visible'); $full_requested_width += $spacing if $first_cell; $full_requested_width += $info->{requested_width}; $expand_cell_count++ if $info->{expand}; $first_cell = FALSE;; } my $extra_space = $allocation->width - $full_requested_width; if ($extra_space < 0) { $extra_space = 0; } elsif ($extra_space > 0 && $expand_cell_count > 0) { $extra_space /= $expand_cell_count; } foreach my $info (@{ $self->{cells} }) { next unless $info->{from} eq 'start'; next unless $info->{renderer}->get ('visible'); $info->{real_width} = $info->{requested_width} + ($info->{expand} ? $extra_space : 0); } foreach my $info (@{ $self->{cells} }) { next unless $info->{from} eq 'end'; next unless $info->{renderer}->get ('visible'); $info->{real_width} = $info->{requested_width} + ($info->{expand} ? $extra_space : 0); } } sub do_expose_event { my ($self, $event) = @_; my $rtl = $self->get_direction eq 'rtl'; return FALSE unless $self->drawable; # "blank" # XXX if ($self->{displayed_row}) { $self->_set_cell_data () } elsif ($self->{model}) { return FALSE; } my $area = Gtk2::Gdk::Rectangle->new ($self->allocation->values); $area->x = $self->allocation->width if $rtl; my $state = $self->state eq 'prelight' ? ['prelit'] : []; foreach my $info (@{ $self->{cells} }) { next unless $info->{from} eq 'start'; next unless $info->{renderer}->get ('visible'); $area->width ($info->{real_width}); #$area->x -= $area->width if $rtl; $info->{renderer}->render ($event->window, $self, $area, $area, $event->area, $state); $area->x ($area->x + $info->{real_width}) unless $rtl; } $area->x ($rtl ? $self->allocation->x : $self->allocation->x + $self->allocation->width); foreach my $info (@{ $self->{cells} }) { next unless $info->{from} eq 'end'; next unless $info->{renderer}->get ('visible'); $area->width ($info->{real_width}); $area->x ($area->x - $area->width) if !$rtl; $info->{renderer}->render ($event->window, $self, $area, $area, $event->area, $state); $area->x ($area->x + $info->{real_width}) if $rtl; } } sub real_pack { my ($self, $cell, $expand, $from) = @_; push @{ $self->{cells} }, { renderer => $cell, expand => $expand, from => $from, }; } sub PACK_START { my ($self, $cell, $expand) = @_; print "PACK_START @_\n"; real_pack $self, $cell, $expand, 'start'; } sub PACK_END { my ($self, $cell, $expand) = @_; print "PACK_END @_\n"; real_pack $self, $cell, $expand, 'end'; } sub CLEAR { my ($self) = @_; print "CLEAR @_\n"; } sub ADD_ATTRIBUTE { my ($self, $cell, $attribute, $column) = @_; print "ADD_ATTRIBUTE @_\n"; foreach my $info (@{ $self->{cells} }) { if ($info->{renderer} == $cell) { push @{ $info->{attributes} }, { property => $attribute, column => $column, }; } } } sub SET_CELL_DATA_FUNC { my ($self, $cell, $func) = @_; print "SET_CELL_DATA_FUNC @_\n"; } sub CLEAR_ATTRIBUTES { my ($self, $cell) = @_; print "CLEAR_ATTRIBUTES @_\n"; foreach my $info (@{ $self->{cells} }) { if ($info->{renderer} == $cell) { $info->{attributes} = []; } } } sub REORDER { my ($self, $cell, $position) = @_; print "REORDER @_\n"; for (my $i = 0 ; $i < @{ $self->{cells} } ; $i++) { if ($self->{cells}[$i]{renderer} == $cell) { my $info = splice @{ $self->{cells} }, $i, 1; splice @{ $self->{cells} }, $position, 0, $i; return; } } } sub GET_CELLS { my $self = shift; print "GET_CELLS @_\n"; return map { $_->{renderer} } @{ $self->{cells} }; } package main; use strict; use warnings; use Gtk2 -init; use Glib ':constants'; my @stuff = qw(one two three four five six seven eight nine ten); my $model = Gtk2::ListStore->new ('Glib::String', 'Glib::Int'); foreach my $i (0..$#stuff) { $model->set ($model->append, 0, $stuff[$i], 1, $i); } my $layout = MupCellLayout->new (model => $model); my $cell = Gtk2::CellRendererText->new (); $layout->pack_start ($cell, TRUE); $layout->add_attribute ($cell, text => 0); $cell = Gtk2::CellRendererText->new (); $layout->pack_end ($cell, FALSE); $layout->add_attribute ($cell, text => 1); my $combo = Gtk2::ComboBox->new ($model); $cell = Gtk2::CellRendererText->new (); $combo->pack_start ($cell, FALSE); $combo->add_attribute ($cell, text => 0); $cell = Gtk2::CellRendererText->new (); $combo->pack_start ($cell, FALSE); $combo->add_attribute ($cell, text => 1); $combo->signal_connect (changed => sub { $layout->set (displayed_row => $model->get_path ($combo->get_active_iter ())); }); $combo->set_active (2); my $window = Gtk2::Window->new; my $vbox = Gtk2::VBox->new; my $frame = Gtk2::Frame->new; $frame->set_shadow_type ('in'); $window->add ($vbox); $vbox->add ($frame); $frame->add ($layout); $vbox->add ($combo); $window->show_all (); $window->signal_connect (destroy => sub { Gtk2->main_quit }); Gtk2->main;