File Coverage

blib/lib/Gtk2/Ex/WidgetEvents.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, 2011, 2012 Kevin Ryde
2              
3             # This file is part of Gtk2-Ex-WidgetBits.
4             #
5             # Gtk2-Ex-WidgetBits is free software; you can redistribute it and/or modify
6             # it under the terms of the GNU General Public License as published by the
7             # Free Software Foundation; either version 3, or (at your option) any later
8             # version.
9             #
10             # Gtk2-Ex-WidgetBits 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-WidgetBits. If not, see .
17              
18             package Gtk2::Ex::WidgetEvents;
19 1     1   847 use 5.008;
  1         3  
  1         38  
20 1     1   5 use strict;
  1         1  
  1         32  
21 1     1   5 use warnings;
  1         2  
  1         37  
22 1     1   4 use Carp;
  1         1  
  1         78  
23 1     1   7 use Scalar::Util 'refaddr', 'weaken';
  1         1  
  1         88  
24              
25 1     1   481 use Gtk2 1.183; # for Glib::Flags->new and overloaded !=
  0            
  0            
26             use Glib::Ex::SignalIds;
27              
28             # uncomment this to run the ### lines
29             #use Smart::Comments;
30              
31             our $VERSION = 48;
32              
33              
34             # The following fields are hung on a target widget,
35             #
36             # Gtk2::Ex::WidgetEvents.obj_hash
37             # Hashref of WidgetEvents instances targeting this widget, in the form
38             # refaddr($wevents_obj) => $wevents_obj. Values are weakened. Entries
39             # are removed by WidgetEvents DESTROY, including the whole field removed
40             # when the last WidgetEvents on the widget is gone.
41             #
42             # Gtk2::Ex::WidgetEvents.signal_ids
43             # A Glib::Ex::SignalIds object for the 'realize' signal calling
44             # _do_widget_realize(). Removed and disconnected by DESTROY of the last
45             # WidgetEvents object on the widget.
46             #
47             # Gtk2::Ex::WidgetEvents.base_events
48             #
49             # Gtk2::Ex::WidgetEvents.last_win_events
50             #
51             #
52             # The various methods taking events as parameters have arg count checks to
53             # protect against something like $wevents->add('foo','bar') expecting both
54             # those events added. It should instead be a single flags parameter
55             # arrayref like $wevents->add(['foo','bar']). It'd be possible to relax and
56             # accept the former, but everywhere else in Gtk2-Perl demands a single arg
57             # for flags, so don't want to encourage laziness in that area.
58              
59              
60             sub new {
61             my ($class, $widget, $events) = @_;
62             ### WidgetEvents new: "$widget"
63              
64             if (@_ != 2 && @_ != 3) {
65             croak 'WidgetEvents->new(): one or two arguments expected';
66             }
67              
68             # coerce to flags object
69             if (@_ == 2) { $events = []; } # if arg omitted
70             $events = Gtk2::Gdk::EventMask->new ($events);
71              
72             my $self = bless { widget => $widget,
73             events => $events }, $class;
74             weaken ($self->{'widget'});
75              
76             # one 'realize' handler per widget
77             $widget->{__PACKAGE__.'.signal_ids'} ||= do {
78             # X server query for the base events when first acting on this widget
79             # and already realized
80             if (my $win = $widget->window) {
81             $widget->{__PACKAGE__.'.base_events'} = $win->get_events;
82             }
83             Glib::Ex::SignalIds->new
84             ($widget, $widget->signal_connect (realize => \&_do_widget_realize))
85             };
86              
87             # autovivify 'obj_hash'
88             weaken ($widget->{__PACKAGE__.'.obj_hash'}->{refaddr($self)}
89             = $self);
90              
91             _update_widget ($widget);
92             return $self;
93             }
94              
95             sub add {
96             if (@_ != 2) { croak 'WidgetEvents->add(): one argument expected'; }
97             my ($self, $events) = @_;
98             my $old_events = $self->{'events'};
99             if (($self->{'events'} += $events) != $old_events) {
100             _update_widget ($self->{'widget'});
101             }
102             }
103             sub remove {
104             if (@_ != 2) { croak 'WidgetEvents->remove(): one argument expected'; }
105             my ($self, $events) = @_;
106             my $old_events = $self->{'events'};
107             if (($self->{'events'} -= $events) != $old_events) {
108             _update_widget ($self->{'widget'});
109             }
110             }
111              
112             sub DESTROY {
113             my ($self) = @_;
114             ### WidgetEvents DESTROY
115             my $widget = $self->{'widget'} || return; # possible weakening
116              
117             my $href = $widget->{__PACKAGE__.'.obj_hash'};
118             delete $href->{refaddr($self)};
119              
120             _update_widget ($widget); # new event mask
121              
122             # when the last WidgetEvents goes away on $widget remove fields
123             # except keep base_events permanently (is that a good idea?)
124             #
125             ### down to count on widget: scalar(%$href)
126             if (! %$href) {
127             delete @{$widget}{__PACKAGE__.'.signal_ids',
128             __PACKAGE__.'.last_win_events',
129             __PACKAGE__.'.obj_hash'}; # hash slice
130             # __PACKAGE__.'.base_events',
131             }
132             }
133              
134             # 'realize' signal handler
135             sub _do_widget_realize {
136             my ($widget) = @_;
137             ### WidgetEvents realize: "$widget"
138              
139             if (defined (my $base_events = $widget->{__PACKAGE__.'.base_events'})) {
140             $widget->{__PACKAGE__.'.last_win_events'} = $base_events;
141             }
142             _update_widget ($widget);
143             }
144              
145             sub _update_widget {
146             my ($widget) = @_;
147             if (! $widget) { return; } # possible weakening
148             ### WidgetEvents update: "$widget"
149              
150             my $win = $widget->window || return; # nothing to do until realized
151              
152             my $base_events = $widget->{__PACKAGE__.'.base_events'};
153             if (! defined $base_events) {
154             $base_events
155             = $widget->{__PACKAGE__.'.last_win_events'}
156             = $widget->{__PACKAGE__.'.base_events'}
157             = $win->get_events; # X server round-trip
158              
159             ### establish base_events: "$base_events"
160             }
161              
162             my $want_events = $base_events + $widget->get_events;
163             foreach my $obj (values %{$widget->{__PACKAGE__.'.obj_hash'}}) {
164             if (defined $obj) { # possible weakening
165             $want_events += $obj->{'events'};
166             }
167             }
168              
169             my $last_win_events = $widget->{__PACKAGE__.'.last_win_events'};
170             if (! defined $last_win_events) {
171             $last_win_events = $widget->{__PACKAGE__.'.last_win_events'}
172             = $win->get_events; # X server round-trip
173             }
174              
175             if ($want_events != $last_win_events) {
176             $widget->{__PACKAGE__.'.last_win_events'} = $want_events;
177             $win->set_events ($want_events); # XSelectInput to server
178              
179             ### install: "@{[$win->get_events]}"
180             ### which adds: "@{[$win->get_events - $last_win_events]}"
181             ### and removes: "@{[$last_win_events - $win->get_events]}"
182             }
183             }
184              
185             1;
186             __END__