File Coverage

blib/lib/Gtk2/Ex/Xor.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


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-Xor.
4             #
5             # Gtk2-Ex-Xor 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-Xor 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-Xor. If not, see .
17              
18             package Gtk2::Ex::Xor;
19 1     1   940 use 5.008;
  1         4  
  1         63  
20 1     1   6 use strict;
  1         2  
  1         36  
21 1     1   5 use warnings;
  1         1  
  1         34  
22 1     1   7 use Carp;
  1         2  
  1         91  
23 1     1   621 use Gtk2;
  0            
  0            
24             use List::Util;
25              
26             # uncomment this to run the ### lines
27             #use Smart::Comments;
28              
29             our $VERSION = 22;
30              
31             my $cache;
32              
33             # widget/window/colormap/depth
34             # - alternate ways of specifying colormap and depth
35             # foreground/background
36             # - color object R/G/B
37             # - string to parse
38             # - number pixel
39             # - undef for style fg/bg
40             # foreground_xor / background_xor
41             # - same but then xored against $widget->Gtk2_Ex_Xor_background
42             # dash_offset, dash_list
43             # - shared, not otherwise handled by Gtk2::GC
44              
45             sub shared_gc {
46             my (%params) = @_;
47             ### shared_gc()
48             $params{'function'} = 'xor';
49              
50             my $widget = delete $params{'widget'};
51             my $window = delete $params{'window'} || $widget->Gtk2_Ex_Xor_window;
52             my $colormap = delete $params{'colormap'} || $window->get_colormap;
53             my $depth = delete $params{'depth'} || $colormap->get_visual->depth;
54              
55             {
56             my @colors;
57             my $xor_color;
58             foreach my $fb ('fore','back') {
59             my $color;
60             ### color: $fb
61             if (exists $params{"${fb}ground_xor"}) {
62             $color = delete $params{"${fb}ground_xor"};
63             ### param xor: $color
64             if (! defined $xor_color) {
65             $xor_color = $widget->Gtk2_Ex_Xor_background;
66             ### xor pixel: $xor_color->pixel
67             }
68             $color = Gtk2::Gdk::Color->new
69             (0,0,0,
70             $xor_color->pixel
71             ^ _color_lookup($widget,$fb,$colormap,$color)->pixel);
72             } else {
73             $color = delete $params{"${fb}ground"};
74             ### param plain: $color
75             $color = _color_lookup ($widget, $fb, $colormap, $color);
76             }
77             ### resulting color: $color
78             ### resulting color: $color->to_string
79             push @colors, $color;
80             }
81             ($params{'foreground'}, $params{'background'}) = @colors;
82             }
83             ### pixels: sprintf "fg %#x bg %#x", $params{'foreground'}->pixel, $params{'background'}->pixel
84              
85             ### dash_offset: $params{'dash_offset'}
86             if (! $params{'dash_offset'}) { # default 0
87             delete $params{'dash_offset'};
88             }
89             ### dash_list: $params{'dash_list'}
90             if (_dash_list_is_default ($params{'dash_list'})) {
91             delete $params{'dash_list'};
92             }
93              
94             # use plain Gtk2::GC if no dashes
95             if (! $params{'dash_offset'} && ! $params{'dash_list'}) {
96             ### use plain Gtk2-GC
97             return Gtk2::GC->get ($depth, $colormap, \%params);
98             }
99              
100             $cache ||= do {
101             require Tie::RefHash::Weak;
102             tie (my %cache, 'Tie::RefHash::Weak');
103             \%cache
104             };
105             my $key = join (';',
106             "colormap=$colormap;depth=$depth",
107             map { my $value = $params{$_};
108             if (/ground$/) {
109             $value = $value->pixel;
110             } elsif ($_ eq 'dash_list') {
111             $value = join(',',@$value);
112             }
113             "$_=$value"
114             } sort keys %params);
115             return ($cache->{$key} ||= do {
116             ### Xor new gc: $key
117             my $dash_offset = delete $params{'dash_offset'} || 0;
118             my $dash_list = delete $params{'dash_list'} || [4];
119             my $gc = Gtk2::Gdk::GC->new ($window, \%params);
120             $gc->set_dashes ($dash_offset, @$dash_list);
121             $gc
122             });
123             }
124              
125             sub _color_lookup {
126             my ($widget, $fb, $colormap, $color) = @_;
127             ### _color_lookup(): $color
128             if (! defined $color) {
129             my $method = ($fb eq 'fore' ? 'fg' : 'bg');
130             ### widget: $fb
131             ### is: $widget->get_style->$method ($widget->state)
132             return $widget->get_style->$method ($widget->state);
133             }
134             if (ref $color) {
135             # copy so as not to clobber pixel field
136             $color = $color->copy;
137             } elsif (Scalar::Util::looks_like_number($color)) {
138             ### pixel
139             return Gtk2::Gdk::Color->new (0,0,0, $color);
140             } else {
141             ### parse
142             my $str = $color;
143             $color = Gtk2::Gdk::Color->parse ($str)
144             || croak "Cannot parse colour '$str'";
145             }
146             # a shared colour alloc would be friendlier to pseudo-colour visuals, but
147             # if the rest of gtk is using the rgb chunk anyway then may as well do the
148             # same
149             $colormap->rgb_find_color ($color);
150             ### rgb_find_color: $color
151             return $color;
152             }
153              
154              
155             # Return true if arrayref $dash_list is the same as the Gtk2::Gdk::GC
156             # default dashes, which is 4,4. An array of 4 or repetitions of 4 is the
157             # default, any segment length other than 4 is not the same as the default.
158             #
159             sub _dash_list_is_default {
160             my ($dash_list) = @_;
161             return ! (defined $dash_list
162             && List::Util::first {$_ != 4} @$dash_list);
163             }
164             # sub _dash_lists_equal {
165             # my @pos = (0) x @_;
166             # my $maxlen = gcd (map {scalar(@{$_})} @_);
167             # foreach (1 .. $maxlen) {
168             # $want = $_[0]->[$pos[0]++];
169             # if ($pos[0] > @{$_[0]}) { $pos[0] = 0; }
170             #
171             # foreach (@_) {
172             # my ($d1, $d2) = @_;
173             # return ! (defined $dash_list
174             # && List::Util::first {$_ != 4} @$dash_list);
175             # }
176              
177             sub _event_widget_coords {
178             my ($widget, $event) = @_;
179              
180             # Do a get_pointer() to support 'pointer-motion-hint-mask'.
181             # Maybe should use $display->get_state here instead of just get_pointer,
182             # but crosshair and lasso at present only work with the mouse, not an
183             # arbitrary input device.
184             if ($event->can('is_hint') && $event->is_hint) {
185             return $widget->get_pointer;
186             }
187              
188             my $x = $event->x;
189             my $y = $event->y;
190             my $eventwin = $event->window;
191             if ($eventwin != $widget->window) {
192             my ($wx, $wy) = $eventwin->get_position;
193             ### subwindow offset: "$wx,$wy"
194             $x += $wx;
195             $y += $wy;
196             }
197             return ($x, $y);
198             }
199              
200             sub _ref_weak {
201             my ($weak_self) = @_;
202             require Scalar::Util;
203             Scalar::Util::weaken ($weak_self);
204             return \$weak_self;
205             }
206              
207              
208             #------------------------------------------------------------------------------
209             # background colour hacks
210              
211             # default is from the widget's Gtk2::Style, but with an undocumented
212             # 'Gtk2_Ex_Xor_background' as an override
213             #
214             sub Gtk2::Widget::Gtk2_Ex_Xor_background {
215             my ($widget) = @_;
216             if (exists $widget->{'Gtk2_Ex_Xor_background'}) {
217             return $widget->{'Gtk2_Ex_Xor_background'};
218             }
219             return $widget->Gtk2_Ex_Xor_background_from_style;
220             }
221              
222             # "bg" is the background for normal widgets
223             sub Gtk2::Widget::Gtk2_Ex_Xor_background_from_style {
224             my ($widget) = @_;
225             return $widget->get_style->bg ($widget->state);
226             }
227              
228             # "base" is the background for text-oriented widgets like Gtk2::Entry and
229             # Gtk2::TextView. TextView has multiple windows, so this is the colour
230             # meant for the main text window.
231             #
232             # GooCanvas uses the "base" colour too. Dunno if it thinks of itself as
233             # text oriented or if white in the default style colours seemed better.
234             #
235             sub Gtk2::Entry::Gtk2_Ex_Xor_background_from_style {
236             my ($widget) = @_;
237             return $widget->get_style->base ($widget->state);
238             }
239             *Gtk2::TextView::Gtk2_Ex_Xor_background_from_style
240             = \&Gtk2::Entry::Gtk2_Ex_Xor_background_from_style;
241             *Goo::Canvas::Gtk2_Ex_Xor_background_from_style
242             = \&Gtk2::Entry::Gtk2_Ex_Xor_background_from_style;
243              
244             # For Gtk2::Bin subclasses such as Gtk2::EventBox, look at the child's
245             # background if there's a child and if it's a no-window widget, since that
246             # child is what will be xored over.
247             #
248             # Perhaps this should be only some of the Bin classes, like Gtk2::Window,
249             # Gtk2::EventBox and Gtk2::Alignment.
250             {
251             package Gtk2::Bin;
252             sub Gtk2_Ex_Xor_background {
253             my ($widget) = @_;
254             # same override as above ...
255             if (exists $widget->{'Gtk2_Ex_Xor_background'}) {
256             return $widget->{'Gtk2_Ex_Xor_background'};
257             }
258             if (my $child = $widget->get_child) {
259             if ($child->flags & 'no-window') {
260             return $child->Gtk2_Ex_Xor_background;
261             }
262             }
263             return $widget->SUPER::Gtk2_Ex_Xor_background;
264             }
265             }
266              
267              
268             #------------------------------------------------------------------------------
269             # window choice hacks
270              
271             # normal "->window" for most widgets
272             *Gtk2::Widget::Gtk2_Ex_Xor_window = \&Gtk2::Widget::window;
273              
274             # for Gtk2::Layout must draw into its "bin_window"
275             *Gtk2::Layout::Gtk2_Ex_Xor_window = \&Gtk2::Layout::bin_window;
276              
277             sub Gtk2::TextView::Gtk2_Ex_Xor_window {
278             my ($textview) = @_;
279             return $textview->get_window ('text');
280             }
281              
282             # GtkEntry has a window and then within that a subwindow just 4 pixels
283             # smaller in height. The latter is what it draws on.
284             #
285             # The following code as per Gtk2::Ex::WidgetCursor. Since the subwindow
286             # isn't a documented feature check that it does, in fact, exist.
287             #
288             # The alternative would be "include inferiors" on the xor gc's. But that'd
289             # probably cause problems on windowed widget children, since expose events
290             # in them wouldn't be seen by the parent's expose to redraw the
291             # crosshair/lasso/etc.
292             #
293             sub Gtk2::Entry::Gtk2_Ex_Xor_window {
294             my ($widget) = @_;
295             my $win = $widget->window || return undef; # if unrealized
296             return ($win->get_children)[0] # first child
297             || $win;
298             }
299              
300             # GooCanvas draws on a subwindow too, also undocumented it seems
301             # (there's a tmp_window too, but that's only an overlay suppressing some
302             # expose events or something at selected times)
303             *Goo::Canvas::Gtk2_Ex_Xor_window = \&Gtk2::Entry::Gtk2_Ex_Xor_window;
304              
305             1;
306             __END__