File Coverage

blib/lib/Gtk2/Ex/History.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             # Copyright 2007, 2008, 2009, 2010, 2011 Kevin Ryde
2              
3             # This file is part of Gtk2-Ex-History.
4             #
5             # Gtk2-Ex-History is free software; you can redistribute it and/or modify it
6             # under the terms of the GNU General Public License as published by the Free
7             # Software Foundation; either version 3, or (at your option) any later
8             # version.
9             #
10             # Gtk2-Ex-History is distributed in the hope that it will be useful, but
11             # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12             # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13             # for more details.
14             #
15             # You should have received a copy of the GNU General Public License along
16             # with Gtk2-Ex-History. If not, see .
17              
18             package Gtk2::Ex::History;
19 1     1   789 use 5.008;
  1         3  
  1         33  
20 1     1   5 use strict;
  1         2  
  1         28  
21 1     1   5 use warnings;
  1         1  
  1         31  
22 1     1   530 use Gtk2 1.220;
  0            
  0            
23             use POSIX ();
24             use Scalar::Util;
25              
26             use Gtk2;
27             use Glib::Ex::SignalBits;
28             use Glib::Ex::FreezeNotify;
29              
30             # uncomment this to run the ### lines
31             #use Smart::Comments;
32              
33             our $VERSION = 8;
34              
35              
36             # place-to-icon-pixbuf
37             # $h->dialog_class, default sub of self
38             # $h->dialog_popup (parent => ...)
39             # $h->menu_popup (parent => ..., way => ..., event => ...)
40             # MenuBits popup_for_event (parent, event)
41              
42             # place-to-renderers
43             # place-to-cellinfo
44             # place-serialize \ or Storable freeze
45             # place-unserialize /
46              
47             # place-to-selectiondata
48             # default place-to-text
49             # flag for set, or emptiness of SelectionData
50             # selectiondata-to-place
51             # Gtk2::SelectionData
52              
53              
54             use Glib::Object::Subclass
55             'Glib::Object',
56             signals => { 'place-to-text' =>
57             { param_types => ['Glib::Scalar'],
58             return_type => 'Glib::String',
59             flags => ['run-last'],
60             class_closure => \&_default_place_to_text,
61             accumulator => \&Glib::Ex::SignalBits::accumulator_first_defined },
62              
63             'place-equal' =>
64             { param_types => ['Glib::Scalar', 'Glib::Scalar'],
65             return_type => 'Glib::Boolean',
66             flags => ['run-last'],
67             class_closure => \&_default_place_equal,
68             accumulator => \&Glib::Ex::SignalBits::accumulator_first },
69             },
70              
71             properties => [ Glib::ParamSpec->scalar
72             ('current',
73             'Current place object',
74             'Current place object in the history.',
75             Glib::G_PARAM_READWRITE),
76              
77             Glib::ParamSpec->int
78             ('max-history',
79             'Maximum history count',
80             'The maximum number of places to keep in the history (backwards and forwards counted separately currently).',
81             0, # min
82             POSIX::INT_MAX(), # max
83             40, # default
84             Glib::G_PARAM_READWRITE),
85              
86             # this one not documented yet ...
87             Glib::ParamSpec->boolean
88             ('use-markup',
89             'Use markup',
90             'Blurb.',
91             0, # default
92             Glib::G_PARAM_READWRITE),
93             ];
94              
95             BEGIN {
96             Glib::Type->register_enum ('Gtk2::Ex::History::Way',
97             back => 0,
98             forward => 1);
99             }
100              
101             #------------------------------------------------------------------------------
102              
103             sub INIT_INSTANCE {
104             my ($self) = @_;
105              
106             $self->{'current'} = undef;
107              
108             require Gtk2::Ex::History::ListStore;
109             my $back_model = $self->{'back_model'}
110             = Gtk2::Ex::History::ListStore->new;
111              
112             my $forward_model = $self->{'forward_model'}
113             = Gtk2::Ex::History::ListStore->new;
114              
115             my $current_model = $self->{'current_model'}
116             = Gtk2::Ex::History::ListStore->new;
117             $current_model->{'current'} = 1; # flag for ListStore drag/drop
118             Scalar::Util::weaken ($current_model->{'history'} = $self);
119              
120             foreach my $aref ($back_model ->{'others'} = [ $forward_model ],
121             $forward_model->{'others'} = [ $back_model ],
122             $current_model->{'others'} = [ $back_model, $forward_model ]) {
123             foreach (@$aref) {
124             Scalar::Util::weaken ($_);
125             }
126             }
127             ### models: { back => $back_model, forward => $forward_model, current => $current_model }
128             }
129              
130             sub SET_PROPERTY {
131             my ($self, $pspec, $newval) = @_;
132             my $pname = $pspec->get_name;
133             if ($pname eq 'current') {
134             $self->goto ($newval);
135             } else {
136             $self->{$pname} = $newval;
137             }
138             }
139              
140             sub _default_place_to_text {
141             my ($self, $place) = @_;
142             return "$place";
143             }
144             sub _default_place_equal {
145             my ($self, $k1, $k2) = @_;
146             ### _default_place_equal(): ($k1 eq $k2)
147             if (defined $k1) {
148             return (defined $k2 && $k1 eq $k2);
149             } else {
150             return (! defined $k2);
151             }
152             }
153              
154             #-----------------------------------------------------------------------------
155              
156             # this one not documented yet
157             sub model {
158             my ($self, $way) = @_;
159             return $self->{"${way}_model"};
160             }
161              
162             sub remove {
163             my ($self, $place) = @_;
164             require Gtk2::Ex::TreeModelBits;
165             Gtk2::Ex::TreeModelBits->VERSION(16); # for extra remove args
166             foreach my $model ($self->{'back_model'}, $self->{'forward_model'}) {
167             Gtk2::Ex::TreeModelBits::remove_matching_rows
168             ($model, \&_do_remove_match, [$self, $place]);
169             }
170             }
171             sub _do_remove_match {
172             my ($model, $iter, $userdata) = @_;
173             my ($self, $place) = @$userdata;
174             return $self->signal_emit ('place-equal',
175             $place,
176             $model->get_value ($iter, $model->COL_PLACE));
177             }
178              
179             #-----------------------------------------------------------------------------
180              
181             sub _set_current {
182             my ($self, $place) = @_;
183             my $model = $self->{'current_model'};
184             my $iter = $model->get_iter_first || $model->append;
185             $model->set ($iter, $model->COL_PLACE, $place);
186             $self->{'current'} = $place;
187             $self->notify('current');
188             }
189              
190             sub goto {
191             my ($self, $place) = @_;
192             ### history goto: $place
193              
194             my $current = $self->{'current'};
195             if (defined $current) {
196             if ($self->signal_emit ('place-equal', $current, $place)) {
197             ### same as current
198             return;
199             }
200             ### push back_model: $current
201             my $back_model = $self->{'back_model'};
202             $back_model->insert_with_values (0, $back_model->COL_PLACE, $current);
203             _limit ($self, $back_model);
204             }
205             _set_current ($self, $place);
206             }
207              
208             sub back {
209             my ($self, $n) = @_;
210             if (! defined $n) { $n = 1; }
211             ### History back: $n
212              
213             my $current = $self->{'current'};
214             if ($n > 0) {
215             my $back_model = $self->{'back_model'};
216             my $forward_model = $self->{'forward_model'};
217             while ($n-- > 0) {
218             my $iter = $back_model->get_iter_first || do {
219             ### no more back
220             last;
221             };
222             my $place = $back_model->get_value ($iter, $back_model->COL_PLACE);
223             ### back to: $place
224             $back_model->remove ($iter);
225              
226             ### push forward: $current
227             $forward_model->insert_with_values (0, $back_model->COL_PLACE, $current);
228             _limit ($self, $forward_model);
229              
230             $current = $place;
231             }
232             ### back set current to: $place
233             _set_current ($self, $current);
234             }
235             ### back at: $current
236             return $current;
237             }
238              
239             sub forward {
240             my ($self, $n) = @_;
241             if (! defined $n) { $n = 1; }
242             ### History forward: $n
243              
244             my $freezer = Glib::Ex::FreezeNotify->new ($self); # hold off 'current' prop
245             if ($n > 0) {
246             my $forward_model = $self->{'forward_model'};
247             while ($n--) {
248             my $iter = $forward_model->get_iter_first || last;
249             my $place = $forward_model->get_value ($iter, $forward_model->COL_PLACE);
250             $forward_model->remove ($iter);
251              
252             $self->goto ($place);
253             }
254             }
255             ### History forward to: $self->{'current'}
256             return $self->{'current'};
257             }
258              
259             # enforce 'max-history' on the given liststore model
260             # if it's too big then remove elements from the end
261             sub _limit {
262             my ($self, $model) = @_;
263             ### _limit to: $self->get('max-history'), "$model"
264             my $len = $model->iter_n_children (undef);
265             my $max = $self->get('max-history');
266             for (my $pos = $len - 1; $pos >= $max; $pos--) {
267             $model->remove ($model->iter_nth_child (undef, $pos));
268             }
269             }
270              
271              
272             1;
273             __END__