File Coverage

blib/lib/Gtk2/Ex/CellLayout/Base.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             # Copyright 2007, 2008, 2009, 2010 Kevin Ryde
2              
3             # This file is part of Gtk2-Ex-CellLayout-Base.
4             #
5             # Gtk2-Ex-CellLayout-Base is free software; you can redistribute it and/or
6             # modify it under the terms of the GNU General Public License as published
7             # by the Free Software Foundation; either version 3, or (at your option) any
8             # later version.
9             #
10             # Gtk2-Ex-CellLayout-Base is distributed in the hope that it will be useful,
11             # but WITHOUT ANY WARRANTY; without even the implied warranty of
12             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13             # Public License for more details.
14             #
15             # You should have received a copy of the GNU General Public License along
16             # with Gtk2-Ex-CellLayout-Base. If not, see .
17              
18             package Gtk2::Ex::CellLayout::Base;
19 1     1   707 use 5.008;
  1         2  
  1         29  
20 1     1   4 use strict;
  1         1  
  1         20  
21 1     1   4 use warnings;
  1         1  
  1         17  
22 1     1   4 use Carp;
  1         1  
  1         68  
23 1     1   5 use List::Util;
  1         1  
  1         64  
24 1     1   1487 use Gtk2;
  0            
  0            
25             use Glib::Ex::FreezeNotify;
26              
27             our $VERSION = 5;
28              
29              
30             #------------------------------------------------------------------------------
31             # Gtk2::CellLayout interface functions
32              
33             # gtk_cell_layout_pack_start
34             #
35             # Because calls here have been spun through GInterface, $expand will be a
36             # the usual Gtk2-Perl representation of a boolean, ie. either '' or 1.
37             #
38             sub PACK_START {
39             my ($self, $cell, $expand) = @_;
40             my $cellinfo_list = ($self->{'cellinfo_list'} ||= []);
41             if (List::Util::first {$_->{'cell'} == $cell} @$cellinfo_list) {
42             croak "this cell renderer already packed into this widget";
43             }
44             push @$cellinfo_list, { cell => $cell,
45             pack => 'start',
46             expand => $expand };
47             $self->_cellinfo_list_changed;
48             }
49              
50             # gtk_cell_layout_pack_end
51             sub PACK_END {
52             my ($self, $cell, $expand) = @_;
53             my $cellinfo_list = ($self->{'cellinfo_list'} ||= []);
54             if (List::Util::first {$_->{'cell'} == $cell} @$cellinfo_list) {
55             croak "this cell renderer already packed into this widget";
56             }
57             push @$cellinfo_list, { cell => $cell,
58             pack => 'end',
59             expand => $expand };
60             $self->_cellinfo_list_changed;
61             }
62              
63             # gtk_cell_layout_clear
64             sub CLEAR {
65             my ($self) = @_;
66             $self->{'cellinfo_list'} = [];
67             $self->_cellinfo_list_changed;
68             }
69              
70             # gtk_cell_layout_add_attribute
71             #
72             # The core widgets like Gtk2::TreeViewColumn seem a bit slack in their
73             # add_attributes() when the property name is the same as previously added.
74             # They do a list "prepend" and so end up with the oldest setting applied
75             # last and thus having priority. It's hard to believe that's right, either
76             # an error or the latest setting would surely have to be right. For now we
77             # plonk into the hash so a new setting overwrites any previous.
78             #
79             sub ADD_ATTRIBUTE {
80             my ($self, $cell, $attribute, $column) = @_;
81             my $cellinfo = $self->_get_cellinfo_for_cell ($cell);
82             $cellinfo->{'attributes'}->{$attribute} = $column;
83             $self->_cellinfo_attributes_changed;
84             }
85              
86             # gtk_cell_layout_set_cell_data_func
87             sub SET_CELL_DATA_FUNC {
88             my ($self, $cell, $func, $userdata) = @_;
89             my $cellinfo = $self->_get_cellinfo_for_cell ($cell);
90             $cellinfo->{'datafunc'} = $func;
91             $cellinfo->{'datafunc_userdata'} = $userdata;
92             $self->_cellinfo_attributes_changed;
93             }
94              
95             # gtk_cell_layout_clear_attributes
96             sub CLEAR_ATTRIBUTES {
97             my ($self, $cell) = @_;
98             my $cellinfo = $self->_get_cellinfo_for_cell ($cell);
99             %{$cellinfo->{'attributes'}} = ();
100             $self->_cellinfo_attributes_changed;
101             }
102              
103             # gtk_cell_layout_reorder
104             sub REORDER {
105             my ($self, $cell, $position) = @_;
106             my $cellinfo_list = $self->{'cellinfo_list'};
107             foreach my $i (0 .. $#$cellinfo_list) {
108             if ($cellinfo_list->[$i]->{'cell'} == $cell) {
109             if ($i == $position) {
110             return; # already in the right position
111             }
112             my $cellinfo = splice @$cellinfo_list, $i, 1;
113             splice @$cellinfo_list, $position,0, $cellinfo;
114             $self->_cellinfo_list_changed;
115             return;
116             }
117             }
118             croak "cell renderer not in this widget";
119             }
120              
121             # gtk_cell_layout_get_cells (new in Gtk 2.12)
122             sub GET_CELLS {
123             my ($self) = @_;
124             return map {$_->{'cell'}} @{$self->{'cellinfo_list'}};
125             }
126              
127             # return the cellinfo record containing the renderer $cell
128             sub _get_cellinfo_for_cell {
129             my ($self, $cell) = @_;
130             return ((List::Util::first {$_->{'cell'} == $cell}
131             @{$self->{'cellinfo_list'}})
132             || croak "cell renderer not in this widget");
133             }
134              
135              
136             #------------------------------------------------------------------------------
137             # extra functions
138              
139             # When setting cell data, GtkCellView, GtkIconView and GtkTreeViewColumn all
140             # apply attributes first then run the function, so do the same here.
141             #
142             # The freeze_notify across the attributes and func is the same as the core
143             # viewers. Don't know what it's for, maybe just the general principle of
144             # holding back notifying a group of property changes until all are done.
145             #
146             # The plain hash used for $cellinfo->{'attributes'} gives the property name
147             # keys in no particular order. That should be fine, nobody should expect a
148             # particular order.
149             #
150             # The FreezeNotify mechanism protects against an error throw leaving the
151             # renderer permanently frozen. Errors could arise from dodgy value types to
152             # the $cell->set(), and can arise easily from application code in $func.
153             #
154             # Freezing is not done if there's no attributes to set and no func to call.
155             # This will be unusual, but it saves a couple of cycles on a purely fixed
156             # renderer like for instance a pixbuf icon.
157             #
158             sub _set_cell_data {
159             my ($self, $iter, @extra_settings) = @_;
160             my $model = $self->{'model'} or return;
161              
162             foreach my $cellinfo (@{$self->{'cellinfo_list'}}) {
163             my $cell = $cellinfo->{'cell'};
164             my $freezer;
165              
166             if (my $ahash = $cellinfo->{'attributes'}) {
167             if (my @settings = %$ahash) {
168             $freezer = Glib::Ex::FreezeNotify->new ($cell);
169             for (my $i = 1; $i < @settings; $i += 2) {
170             $settings[$i] = $model->get_value ($iter, $settings[$i]);
171             }
172             $cell->set (@settings);
173             }
174             }
175             if (@extra_settings) {
176             $freezer ||= Glib::Ex::FreezeNotify->new ($cell);
177             $cell->set (@extra_settings);
178             }
179             if (my $func = $cellinfo->{'datafunc'}) {
180             $freezer ||= Glib::Ex::FreezeNotify->new ($cell);
181             $func->($self, $cell, $model, $iter, $cellinfo->{'datafunc_userdata'});
182             }
183             }
184             }
185              
186             sub _cellinfo_starts {
187             my ($self) = @_;
188             return grep {$_->{'pack'} eq 'start'} @{$self->{'cellinfo_list'}};
189             }
190             sub _cellinfo_ends {
191             my ($self) = @_;
192             return grep {$_->{'pack'} ne 'start'} @{$self->{'cellinfo_list'}};
193             }
194              
195             #------------------------------------------------------------------------------
196             # overridable class callouts
197              
198             # Not yet 100% certain about these "changed" methods.
199             # It's maybe, perhaps, possibly, kinda, sorta going to be like the following
200             # ...
201              
202             sub _cellinfo_list_changed {
203             my ($self) = @_;
204             $self->queue_resize;
205             $self->queue_draw;
206             }
207              
208             sub _cellinfo_attributes_changed {
209             my ($self) = @_;
210             $self->queue_resize;
211             $self->queue_draw;
212             }
213              
214             # =head1 METHOD OVERRIDES
215             #
216             # The recommended C shown in the synopsis above brings
217             # C into your C<@ISA> in the usual way, and also
218             # in the usual way you can override the functions in C with
219             # your own implementations, probably chaining up to the base ones, perhaps
220             # not. The following methods in particular can be intercepted,
221             #
222             # =over 4
223             #
224             # =item C<< $self->_cellinfo_list_changed () >>
225             #
226             # This method is called by C, C, C and C
227             # to indicate that the list of renderers has changed. The default
228             # implementation does a C and C, expecting a new
229             # size for new renderers and of course new drawing.
230             #
231             # Viewer widget code might add to this if it for instance maintains additional
232             # data relating to the renderers.
233             #
234             # =item C<< $self->_cellinfo_attributes_changed () >>
235             #
236             # This method is called by C, C and
237             # C to indicate that renderer attributes have changed.
238             # The default implementation does a C and C,
239             # expecting a possible new size or new drawing from different attributes.
240             #
241             # Viewer widget code might add to this, for instance to invalidate renderer
242             # display size data if maybe it tries to cache that for on-screen or nearby
243             # rows.
244             #
245             # =back
246              
247              
248             #------------------------------------------------------------------------------
249             # Gtk2::Buildable interface functions
250              
251             # This is the same as _gtk_cell_layout_buildable_add_child(), in particular
252             # as per that function it always does "pack_start" and false for the expand
253             # parameter.
254             #
255             sub ADD_CHILD {
256             my ($self, $builder, $child, $type) = @_;
257             $self->pack_start ($child, 0);
258             }
259              
260             # In a ... block under one of our new viewer widget, we
261             # handle ... to make add_attributes calls.
262             #
263             # $self is a viewer widget, ie. someone using us, and $child is the
264             # Gtk2::CellRenderer child being added to the viewer.
265             #
266             sub CUSTOM_TAG_START {
267             my ($self, $builder, $child, $tagname) = @_;
268             if ($child && $tagname eq 'attributes') {
269             require Gtk2::Ex::CellLayout::BuildAttributes;
270             return Gtk2::Ex::CellLayout::BuildAttributes->new (cell_layout => $self,
271             cell_renderer=> $child);
272             } else {
273             return undef;
274             }
275             }
276              
277              
278             1;
279             __END__