File Coverage

blib/lib/Gtk2/Ex/VolumeButton.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Gtk2::Ex::VolumeButton;
2              
3 1     1   19855 use strict;
  1         2  
  1         33  
4 1     1   4 use warnings;
  1         1  
  1         25  
5 1     1   385 use Glib qw( TRUE FALSE );
  0            
  0            
6             use Gtk2;
7             use Gtk2::Gdk::Keysyms;
8              
9             our $VERSION = '0.07';
10              
11             use Glib::Object::Subclass
12             Gtk2::ToggleButton::,
13             signals => {
14             volume_changed => {
15             flags => [qw( run-last )],
16             return_type => undef,
17             param_types => [qw( Glib::Int )]
18             },
19             mute_changed => {
20             flags => [qw( run-last )],
21             return_type => undef,
22             param_types => [qw( Glib::Boolean )]
23             },
24             show => \&on_show
25             },
26             properties => [
27             Glib::ParamSpec->int(
28             'volume',
29             'Volume',
30             'Current volume',
31             0,
32             100,
33             50,
34             [qw( readable writable )]
35             ),
36             Glib::ParamSpec->string(
37             'mute_image',
38             'Mute Image',
39             'Image to display when the volume is muted',
40             '',
41             [qw( readable writable )]
42             ),
43             Glib::ParamSpec->string(
44             'zero_image',
45             'Zero Image',
46             'Image to display when the volume is zero',
47             '',
48             [qw( readable writable )]
49             ),
50             Glib::ParamSpec->string(
51             'min_image',
52             'Min Image',
53             'Image to display when the volume is minimal',
54             '',
55             [qw( readable writable )]
56             ),
57             Glib::ParamSpec->string(
58             'medium_image',
59             'Medium Image',
60             'Image to display when the volume is medium',
61             '',
62             [qw( readable writable )]
63             ),
64             Glib::ParamSpec->string(
65             'max_image',
66             'Max Image',
67             'Image to display when the volume is maximal',
68             '',
69             [qw( readable writable )]
70             ),
71             Glib::ParamSpec->int(
72             'icon_size',
73             'Icon Size',
74             'Size of the icons',
75             0,
76             1000,
77             18,
78             [qw( readable writable )]
79             ),
80             Glib::ParamSpec->string(
81             'position',
82             'Position',
83             'Position of the popup window',
84             'bottom',
85             [qw( readable writable )]
86             ),
87             Glib::ParamSpec->boolean(
88             'muted',
89             'Muted',
90             'Is the volume muted?',
91             0,
92             [qw( readable writable )]
93             )
94             ];
95              
96             sub INIT_INSTANCE {
97             my $self = shift;
98              
99             $self->{volume} = 0;
100             $self->{icon_size} = 18;
101             $self->{position} = 'buttom';
102            
103             $self->signal_connect( 'toggled', \&toggle_cb );
104             $self->signal_connect( 'scroll_event', \&scroll_event_cb );
105              
106             $self->{image} = Gtk2::Image->new();
107             $self->{image}->show();
108             $self->add( $self->{image} );
109             }
110              
111             sub update_pixbufs {
112             my $self = shift;
113              
114             for(qw( mute zero min medium max )) {
115             if( ref $self->{$_.'_image'} && $self->{$_.'_image'}->isa('Gtk2::Gdk::Pixbuf') ) {
116             $self->{pixbufs}->{$_} = $self->{$_.'_image'};
117             } elsif( -r $self->{$_.'_image'} ) {
118             $self->{pixbufs}->{$_} = Gtk2::Gdk::Pixbuf->new_from_file_at_size( $self->{$_.'_image'}, $self->{icon_size}, $self->{icon_size} );
119             } elsif($_) {
120             $self->{pixbufs}->{$_} = $self->render_icon( $self->{$_.'_image'}, $self->{icon_size} );
121             }
122             }
123             }
124              
125             sub on_show {
126             my $self = shift;
127             $self->update_pixbufs();
128             $self->update_image( $self->{volume} );
129             $self->signal_chain_from_overridden();
130             }
131              
132             sub _CLAMP {
133             my( $x, $min, $max ) = @_;
134              
135             return $max if $x > $max;
136             return $min if $x < $min;
137             return $x;
138             }
139              
140             sub _MAX {
141             my( $a, $b ) = @_;
142              
143             return ($a > $b) ? $a : $b;
144             }
145              
146             sub scale_key_press_cb {
147             my( undef, $event, $self ) = @_;
148              
149             if( $event->keyval == $Gtk2::Gdk::Keysyms{Escape} ) {
150             $self->hide_scale();
151             $self->set_volume( $self->{revert_volume} );
152             return TRUE;
153             } elsif($event->keyval == $Gtk2::Gdk::Keysyms{Return} ||
154             $event->keyval == $Gtk2::Gdk::Keysyms{space} ) {
155             $self->hide_scale();
156             return TRUE;
157             }
158              
159             return FALSE;
160             }
161              
162             sub scale_value_changed_cb {
163             my( $widget, $self ) = @_;
164            
165             my $vol = $widget->get_value();
166             $vol = _CLAMP( $vol, 0, 100 );
167              
168             $self->{volume} = $vol;
169             $self->update_image($vol);
170              
171             $self->signal_emit( 'volume_changed', $vol );
172             }
173              
174             sub popup_button_press_event_cb {
175             my( undef, undef, $self ) = @_;
176            
177             if( $self->{popup} ) {
178             $self->hide_scale();
179             return TRUE;
180             }
181              
182             return FALSE;
183             }
184              
185             sub show_scale {
186             my $self = shift;
187              
188             $self->{popup} = Gtk2::Window->new('popup');
189             $self->{popup}->set_screen( $self->get_screen );
190              
191             $self->{revert_volume} = $self->{volume};
192              
193             my $frame = Gtk2::Frame->new();
194             $frame->set_border_width(0);
195             $frame->set_shadow_type('out');
196             $frame->show();
197              
198             $self->{popup}->add($frame);
199              
200             my $box = Gtk2::VBox->new( FALSE, 0 );
201             $box->show();
202              
203             $frame->add($box);
204              
205             my $adj = Gtk2::Adjustment->new( $self->{volume}, 0, 100, 5, 10, 0 );
206             $self->{scale} = Gtk2::VScale->new($adj);
207             $self->{scale}->set_draw_value(FALSE);
208             $self->{scale}->set_update_policy('continuous');
209             $self->{scale}->set_inverted(TRUE);
210             $self->{scale}->show();
211              
212             $self->{popup}->signal_connect( 'button_press_event',
213             \&popup_button_press_event_cb, $self);
214             $self->{scale}->signal_connect( 'key_press_event', \&scale_key_press_cb,
215             $self );
216             $self->{scale}->signal_connect( 'value_changed', \&scale_value_changed_cb,
217             $self );
218              
219             my $label = Gtk2::Label->new('+');
220             $label->show();
221             $box->pack_start( $label, FALSE, TRUE, 0 );
222              
223             $label = Gtk2::Label->new('-');
224             $label->show();
225             $box->pack_end( $label, FALSE, TRUE, 0 );
226              
227             $box->pack_start( $self->{scale}, TRUE, TRUE, 0 );
228              
229             my $req = $self->{popup}->size_request();
230             my($x, $y) = $self->window->get_origin();
231             my $alloc = $self->allocation();
232              
233             $req->width( _MAX($req->width, $alloc->width) );
234              
235             $self->{scale}->set_size_request( -1, 100 );
236             $self->{popup}->set_size_request( $req->width, -1 );
237              
238             $x += $alloc->x;
239              
240             if( $self->{position} eq 'bottom' ) {
241             $y += $alloc->y + $alloc->height;
242             } else {
243             $y -= ($self->{popup}->get_size())[1];
244             }
245              
246             $x = _MAX( 0, $x );
247             $y = _MAX( 0, $y );
248              
249             $self->{popup}->move($x, $y);
250             $self->{popup}->show();
251              
252             $self->{popup}->grab_focus();
253             Gtk2->grab_add( $self->{popup} );
254              
255             my $grabbed = Gtk2::Gdk->pointer_grab(
256             $self->{popup}->window, TRUE,
257             [qw( button-press-mask button-release-mask pointer-motion-mask )],
258             undef, undef, Gtk2->get_current_event_time() );
259              
260             if( $grabbed eq 'success' ) {
261             $grabbed = Gtk2::Gdk->keyboard_grab( $self->{popup}->window, TRUE,
262             Gtk2->get_current_event_time() );
263              
264             $grabbed = 'success';
265             unless( $grabbed eq 'success' ) {
266             Gtk2->grab_remove( $self->{popup} );
267             $self->{popup}->destroy();
268             }
269             } else {
270             Gtk2->grab_remove( $self->{popup} );
271             $self->{popup}->destroy();
272             }
273             }
274              
275             sub hide_scale {
276             my $self = shift;
277              
278             if( $self->{popup} ) {
279             Gtk2->grab_remove( $self->{popup} );
280             Gtk2::Gdk->pointer_ungrab( Gtk2->get_current_event_time() );
281             Gtk2::Gdk->keyboard_ungrab( Gtk2->get_current_event_time() );
282             $self->{popup}->destroy();
283             }
284              
285             if( $self->get_active() ) {
286             $self->set_active(FALSE);
287             }
288             }
289              
290             sub toggle_cb {
291             my $self = shift;
292              
293             if( $self->get_active() ) {
294             $self->show_scale();
295             } else {
296             $self->hide_scale();
297             }
298             }
299              
300             sub scroll_event_cb {
301             my($self, $event) = @_;
302              
303             my $vol = $self->{volume};
304              
305             if( $event->direction eq 'up' ) {
306             $vol += 10;
307             } elsif( $event->direction eq 'down' ) {
308             $vol -= 10;
309             } else {
310             return;
311             }
312              
313             $vol = _CLAMP( $vol, 0, 100 );
314              
315             $self->set_volume($vol);
316             $self->update_image($vol);
317            
318             return TRUE;
319             }
320              
321             sub set_volume {
322             my($self, $vol) = @_;
323              
324             return if $self->{volume} == $vol;
325              
326             $self->{volume} = $vol;
327             $self->update_image( $vol );
328             $self->signal_emit( 'volume_changed', $vol );
329             }
330              
331             sub update_image {
332             my($self, $vol) = @_;
333             my $id;
334            
335             $vol = $self->{volume} unless defined $vol;
336              
337             if( $vol <= 0 ) {
338             $id = 'zero';
339             } elsif( $vol <= 100 / 3 ) {
340             $id = 'min';
341             } elsif( $vol <= 2 * 100 / 3 ) {
342             $id = 'medium';
343             } else {
344             $id = 'max';
345             }
346              
347             my $pixbuf = $self->{pixbufs}->{$id}->copy();
348              
349             if( $self->{muted} ) {
350             $self->{pixbufs}->{mute}->composite(
351             $pixbuf,
352             0,
353             0,
354             $self->{pixbufs}->{mute}->get_width(),
355             $self->{pixbufs}->{mute}->get_height(),
356             0,
357             0,
358             1.0,
359             1.0,
360             'bilinear',
361             255
362             );
363             }
364              
365             $self->{image}->set_from_pixbuf( $pixbuf );
366             }
367              
368             sub toggle_mute {
369             my $self = shift;
370              
371             $self->{muted} = ($self->{muted}) ? 0 : 1;
372             $self->signal_emit( 'mute_changed', $self->{muted} );
373              
374             $self->update_image();
375             }
376              
377             1;
378              
379             __END__