File Coverage

blib/lib/Gtk2/Ex/CrossHair.pm
Criterion Covered Total %
statement 22 24 91.6
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 30 32 93.7


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 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-Xor is distributed in the hope that it will be useful, but WITHOUT
11             # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12             # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13             # 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::CrossHair;
19 2     2   43054 use 5.008;
  2         10  
  2         239  
20 2     2   15 use strict;
  2         5  
  2         86  
21 2     2   12 use warnings;
  2         6  
  2         85  
22 2     2   13 use Carp;
  2         3  
  2         274  
23 2     2   14 use List::Util;
  2         4  
  2         172  
24 2     2   10 use Scalar::Util 1.18 'refaddr'; # 1.18 for pure-perl refaddr() fix
  2         59  
  2         176  
25 2     2   3170 use POSIX ();
  2         19722  
  2         61  
26              
27             # 1.200 for Gtk2::GC auto-release
28 2     2   3034 use Gtk2 1.200;
  0            
  0            
29             use Glib::Ex::SignalIds;
30             use Gtk2::Ex::Xor;
31             use Gtk2::Ex::WidgetBits 31; # v.31 for xy_root_to_widget()
32              
33             # uncomment this to run the ### lines
34             #use Smart::Comments;
35              
36             our $VERSION = 22;
37              
38             # In each CrossHair the private fields are
39             #
40             # xy_widget
41             # The widget to report in the 'moved' signal, or undef.
42             #
43             # root_x,root_y
44             # The current or pending x,y of the crosshair in root coordinates.
45             # root_x is undef if the crosshair is outside any widget and therefore
46             # not to be drawn (in "keyboard" mode rather than implicit-grab button
47             # mode).
48             #
49             # xy_widget and root_x,root_y are set immediately by _maybe_move() for
50             # a mouse motion etc, but the actual drawing of them is later in the
51             # sync_call_handler().
52             #
53             # wcursor
54             # Gtk2::Ex::WidgetCursor object putting an invisible cursor in the
55             # crosshair widgets.
56             #
57             # The _pw() func gives a hash of per-widget data. Its fields are
58             #
59             # static_ids
60             # Glib::Ex::SignalIds of signal connections made for as long as the
61             # widget is in the crosshair.
62             # dynamic_ids
63             # Glib::Ex::SignalIds of signal connections made only while the
64             # crosshair is active.
65             # gc
66             # A Gtk2::GC shared gc to draw with. Created by the _draw() code when
67             # needed, deleted by style-set etc for colour changes etc.
68             # x,y
69             # Position in widget coordinates at which the crosshair is drawn in
70             # the widget. 'x' doesn't exist in the hash if the position is not
71             # yet decided. 'x' is undef if the cross is entirely outside the
72             # widget and thus there's nothing to draw.
73             #
74             # The per-widget data could be in a Tie::RefHash or inside-out thingie or
75             # similar to keep out of the target widgets. Would that be worthwhile? The
76             # widget already has a handy hash to put things in, may as well use that
77             # than load extra code.
78             #
79              
80             use Glib::Object::Subclass
81             'Glib::Object',
82             signals => { moved => { param_types => ['Gtk2::Widget',
83             'Glib::Scalar',
84             'Glib::Scalar'],
85             return_type => undef },
86             },
87             properties => [ Glib::ParamSpec->scalar
88             ('widgets',
89             'Widgets',
90             'Arrayref of widgets to act on.',
91             Glib::G_PARAM_READWRITE),
92              
93             Glib::ParamSpec->object
94             ('widget',
95             'Widget',
96             'Single widget to act on.',
97             'Gtk2::Widget',
98             Glib::G_PARAM_READWRITE),
99              
100             Glib::ParamSpec->object
101             ('add-widget',
102             'Add widget',
103             'Add a widget to act on.',
104             'Gtk2::Widget',
105             'writable'),
106              
107             Glib::ParamSpec->boolean
108             ('active',
109             'Active',
110             'Whether to display the crosshair.',
111             0,
112             Glib::G_PARAM_READWRITE),
113              
114             Glib::ParamSpec->scalar
115             ('foreground',
116             (do { # translation from Gtk2::TextTag
117             my $str = 'Foreground colour';
118             eval { require Locale::Messages;
119             Locale::Messages::dgettext('gtk20-properties',$str)
120             } || $str }),
121             'The colour to draw the crosshair, either a string name (including hex RGB), a Gtk2::Gdk::Color, or undef for the widget\'s style foreground.',
122             Glib::G_PARAM_READWRITE),
123              
124             Glib::ParamSpec->string
125             ('foreground-name',
126             (do { # translation from Gtk2::TextTag
127             my $str = 'Foreground colour name';
128             eval { require Locale::Messages;
129             Locale::Messages::dgettext('gtk20-properties',$str)
130             } || $str }),
131             'The colour to draw the crosshair, as a string colour name.',
132             (eval {Glib->VERSION(1.240);1}
133             ? undef # default
134             : ''), # no undef/NULL before Perl-Glib 1.240
135             Glib::G_PARAM_READWRITE),
136              
137             Glib::ParamSpec->boxed
138             ('foreground-gdk',
139             'Foreground colour object',
140             'The colour to draw the crosshair, as a Gtk2::Gdk::Color object with red,greed,blue fields set (a pixel is looked up on each target widget).',
141             'Gtk2::Gdk::Color',
142             Glib::G_PARAM_READWRITE),
143              
144             Glib::ParamSpec->int
145             ('line-width',
146             'Line width',
147             'The width of the cross lines drawn.',
148             0, POSIX::INT_MAX(), # limits
149             0, # default
150             Glib::G_PARAM_READWRITE),
151             ];
152              
153              
154             sub INIT_INSTANCE {
155             my ($self) = @_;
156             ### CrossHair INIT_INSTANCE
157             $self->{'button'} = 0;
158             $self->{'widgets'} = [];
159             }
160              
161             sub FINALIZE_INSTANCE {
162             my ($self) = @_;
163             ### CrossHair finalize: "$self"
164             $self->end;
165             }
166              
167             sub _pw_list {
168             my ($self) = @_;
169             return values %{$self->{'perwidget'}};
170             }
171             sub _pw {
172             my ($self, $widget) = @_;
173             ### _pw: "@{[$widget||'[undef]']}"
174             return $self->{'perwidget'}->{refaddr($widget)};
175             }
176              
177             sub GET_PROPERTY {
178             my ($self, $pspec) = @_;
179             my $pname = $pspec->get_name;
180             ### CrossHair GET_PROPERTY: $pname
181              
182             if ($pname eq 'widget') {
183             my $widgets = $self->{'widgets'};
184             if (@$widgets > 1) {
185             croak 'Cannot get single \'widget\' property when using multiple widgets';
186             }
187             return $widgets->[0];
188             }
189             if ($pname eq 'foreground_name') {
190             my $foreground = $self->{'foreground'};
191             if (Scalar::Util::blessed($foreground)
192             && $foreground->isa('Gtk2::Gdk::Color')) {
193             $foreground = $foreground->to_string; # string "#RRRRGGGGBBBB"
194             }
195             return $foreground;
196             }
197             if ($pname eq 'foreground_gdk') {
198             my $foreground = $self->{'foreground'};
199             ### $foreground
200             if (defined $foreground
201             && ! Scalar::Util::blessed($foreground)) {
202             # Perl-Glib 1.220 doesn't copy a boxed return like Gtk2::Gdk::Color,
203             # must keep the block of memory in a field
204             $foreground = $self->{'_foreground_gdk'}
205             = Gtk2::Gdk::Color->parse($foreground);
206             }
207             return $foreground;
208             }
209             return $self->{$pname};
210             }
211              
212             sub SET_PROPERTY {
213             my ($self, $pspec, $newval) = @_;
214             my $pname = $pspec->get_name;
215             my $oldval = $self->{$pname};
216             ### CrossHair SET_PROPERTY: $pname
217              
218             if ($pname eq 'widget') {
219             $pname = 'widgets';
220             $newval = [ $newval ];
221             $self->notify ('widgets');
222             } elsif ($pname eq 'add_widget') {
223             $pname = 'widgets';
224             $newval = [ @{$self->{'widgets'}}, $newval ];
225             $self->notify ('widget');
226             $self->notify ('widgets');
227             }
228              
229             if ($pname eq 'widgets') {
230             my $widgets = $newval;
231             ### old widgets: "$self->{'widgets'} @{$self->{'widgets'}}"
232             ### new widgets: "$widgets @$widgets"
233              
234             _undraw ($self);
235              
236             my $old_perwidget = $self->{'perwidget'};
237             my %new_perwidget;
238             $self->{'perwidget'} = \%new_perwidget;
239              
240             foreach my $widget (@$widgets) {
241             my $key = refaddr($widget);
242             ### perwidget key: refaddr($widget)
243             unless ($new_perwidget{$key} = $old_perwidget->{$key}) {
244             _pw_new($self,$widget);
245             }
246             }
247             undef $old_perwidget; # discard pw's of old widgets
248             ### perwidget keys now: keys %{$self->{'perwidget'}}
249              
250             @{$self->{'widgets'}} = @$newval; # copy contents
251              
252             my $xy_widget = $self->{'xy_widget'};
253             my $root_x = $self->{'root_x'};
254             my $root_y = $self->{'root_y'};
255             if ($xy_widget && ! _pw($self,$xy_widget)) {
256             ### xy_widget removed
257             unless ($self->{'xy_widget'} = $xy_widget = List::Util::first
258             {_widget_contains_root_xy ($_, $root_x, $root_y)} @$widgets) {
259             # no drawing when not in any widget, per _do_leave_notify()
260             undef $root_x;
261             undef $root_y;
262             }
263             }
264             _maybe_move ($self, $xy_widget, $root_x, $root_y);
265             _wcursor_update ($self); # new widget set
266             $self->notify ('widget');
267              
268             ### now widgets: "$self->{'widgets'} @{$self->{'widgets'}}"
269             return;
270             }
271              
272             if ($pname eq 'active') {
273             # the extra '$self->notify' calls by running 'start' and 'end' here are
274             # ok, Glib suppresses duplicates while in SET_PROPERTY
275             if ($newval && ! $oldval) {
276             $self->start;
277             } elsif ($oldval && ! $newval) {
278             $self->end;
279             }
280              
281             } elsif ($pname =~ /^foreground/ || $pname eq 'line_width') {
282             if ($pname =~ /^foreground/) {
283             # must copy if 'foreground_gdk' since $newval points to a malloced
284             # copy or something, copy scalar 'foreground' too just in case
285             if (Scalar::Util::blessed($newval)
286             && $newval->isa('Gtk2::Gdk::Color')) {
287             $newval = $newval->copy;
288             }
289             $pname = 'foreground';
290             $self->notify('foreground');
291             $self->notify('foreground-name');
292             $self->notify('foreground-gdk');
293             }
294             _undraw ($self);
295             foreach my $pw (_pw_list($self)) {
296             delete $pw->{'gc'}; # new gc's for colour or width
297             }
298             $self->{$pname} = $newval;
299             _draw ($self);
300             }
301              
302             $self->{$pname} = $newval; # per default GET_PROPERTY
303             }
304              
305             sub _pw_new {
306             my ($self, $widget) = @_;
307             ### _pw_new(): "$widget"
308              
309             # These are events needed in button drag mode, ie. when start() is called
310             # with a button event. The alternative would be to turn them on by a new
311             # Gtk2::Gdk->pointer_grab() to change the implicit grab, though
312             # 'button-release-mask' is best turned on in advance in case we're lagged
313             # and it happens before we change the event mask.
314             #
315             # 'exposure-mask' is not here since if nothing else is drawing then
316             # there's no need for the crosshair to redraw over its changes.
317              
318             require Gtk2::Ex::WidgetEvents;
319             my $wevents = Gtk2::Ex::WidgetEvents->new
320             ($widget, ['button-motion-mask',
321             'button-release-mask',
322             'pointer-motion-mask',
323             'enter-notify-mask',
324             'leave-notify-mask']);
325              
326             my $ref_weak_self = Gtk2::Ex::Xor::_ref_weak ($self);
327             $self->{'perwidget'}->{refaddr($widget)}
328             = { wevents => $wevents,
329             static_ids => Glib::Ex::SignalIds->new
330             ($widget, $widget->signal_connect (style_set => \&_do_style_set,
331             $ref_weak_self)),
332             };
333              
334             if ($self->{'active'}) {
335             _pw_start ($self, $widget);
336             _draw ($self, [$widget]);
337             }
338             }
339              
340             sub start {
341             my ($self, $event) = @_;
342             ### CrossHair start()
343              
344             my $button = $self->{'button'} = (ref $event && $event->can('button')
345             ? $event->button : 0);
346             if ($self->{'active'}) { return; }
347              
348             $self->{'active'} = 1;
349             my $widgets = $self->{'widgets'};
350             _wcursor_update ($self);
351              
352             # initial root_x,root_y from event if given, or by round trip on the first
353             # realized of $widgets otherwise
354             #
355             my ($root_x, $root_y);
356             if (ref $event) {
357             ($root_x, $root_y) = $event->root_coords;
358             } else {
359             foreach my $widget (@$widgets) {
360             my $root_window = $widget->get_root_window || next;
361             ### root_window: "$root_window"
362             (undef, $root_x, $root_y) = $root_window->get_pointer;
363             last;
364             }
365             }
366             ### root x,y: $root_x, $root_y
367              
368             my $xy_widget;
369             if ($button) {
370             # button mode, use reported event widget as $xy_widget, if it's one of
371             # ours
372             my $eventwidget = Gtk2->get_event_widget ($event);
373             $xy_widget = List::Util::first {$_ == $eventwidget} @$widgets;
374              
375             } elsif (defined $root_x) {
376             # Non-button mode, initial $xy_widget as whichever of $widgets contains
377             # the pointer, if any. After this enter and leave events maintain.
378             $xy_widget = List::Util::first
379             {_widget_contains_root_xy ($_, $root_x, $root_y)} @$widgets;
380             if (! defined $xy_widget) {
381             # no drawing when not in any widget, per _do_leave_notify()
382             undef $root_x;
383             undef $root_y;
384             }
385             }
386              
387             $self->{'xy_widget'} = $xy_widget;
388             $self->{'root_x'} = $root_x;
389             $self->{'root_y'} = $root_y;
390              
391             foreach my $widget (@$widgets) {
392             _pw_start ($self, $widget);
393             }
394              
395             $self->notify('active');
396             _sync_call_handler (\$self); # initial drawing immediately
397             }
398              
399             sub _wcursor_update {
400             my ($self) = @_;
401             ### CrossHair _wcursor_update(): "$self"
402             $self->{'wcursor'} = $self->{'active'} && do {
403             require Gtk2::Ex::WidgetCursor;
404             Gtk2::Ex::WidgetCursor->new
405             (widgets => $self->{'widgets'},
406             cursor => 'invisible',
407             active => 1)
408             };
409             }
410              
411             sub _pw_start {
412             my ($self, $widget) = @_;
413             ### CrossHair _pw_start(): "$widget"
414              
415             my $ref_weak_self = Gtk2::Ex::Xor::_ref_weak ($self);
416             _pw($self,$widget)->{'dynamic_ids'} = Glib::Ex::SignalIds->new
417             ($widget,
418             $widget->signal_connect (motion_notify_event => \&_do_motion_notify,
419             $ref_weak_self),
420             $widget->signal_connect (button_release_event => \&_do_button_release,
421             $ref_weak_self),
422             $widget->signal_connect (enter_notify_event => \&_do_enter_notify,
423             $ref_weak_self),
424             $widget->signal_connect (leave_notify_event => \&_do_leave_notify,
425             $ref_weak_self),
426             $widget->signal_connect_after (expose_event => \&_do_expose_event,
427             $ref_weak_self),
428             $widget->signal_connect_after (size_allocate => \&_do_size_allocate,
429             $ref_weak_self));
430             }
431              
432             sub end {
433             my ($self) = @_;
434             if (! $self->{'active'}) { return; }
435              
436             $self->signal_emit ('moved', undef, undef, undef);
437             _undraw ($self);
438             foreach my $pw (_pw_list($self)) {
439             delete $pw->{'dynamic_ids'};
440             }
441             $self->{'active'} = 0;
442             _wcursor_update ($self);
443             $self->notify('active');
444             }
445              
446              
447             #-----------------------------------------------------------------------------
448              
449             # 'motion-notify-event' on a target widget
450             sub _do_motion_notify {
451             my ($widget, $event, $ref_weak_self) = @_;
452             ### CrossHair _do_motion_notify(): "$widget " . $event->x_root . "," . $event->y_root
453             if (my $self = $$ref_weak_self) {
454             if ($self->{'active'}) {
455             _maybe_move ($self, $widget, _event_root_coords ($event));
456             }
457             }
458             return 0; # Gtk2::EVENT_PROPAGATE
459             }
460              
461             # 'size-allocate' signal on a widget
462             sub _do_size_allocate {
463             my ($widget, $alloc, $ref_weak_self) = @_;
464             my $self = $$ref_weak_self || return;
465             ### CrossHair _do_size_allocate: "$widget"
466              
467             # if the widget position has changed then must draw lines at new spots
468             _undraw ($self, [$widget]);
469             _draw ($self, [$widget]);
470             }
471              
472             # 'enter-notify-event' signal on the widgets
473             sub _do_enter_notify {
474             my ($widget, $event, $ref_weak_self) = @_;
475             ### CrossHair _do_enter_notify(): "$widget " . $event->x_root . "," . $event->y_root
476             if (my $self = $$ref_weak_self) {
477             if (! $self->{'button'}) {
478             # not button drag mode
479             _maybe_move ($self, $widget, $event->root_coords);
480             }
481             }
482             return 0; # Gtk2::EVENT_PROPAGATE
483             }
484              
485             # 'leave-notify-event' signal on one of the widgets
486             sub _do_leave_notify {
487             my ($widget, $event, $ref_weak_self) = @_;
488             ### CrossHair _do_leave_notify(): "$widget " . $event->x_root . "," . $event->y_root
489             if (my $self = $$ref_weak_self) {
490             if (! $self->{'button'}) {
491             # not button drag mode
492             _maybe_move ($self, undef, undef, undef);
493             }
494             }
495             return 0; # Gtk2::EVENT_PROPAGATE
496             }
497              
498             # 'button-release-event' signal on one of the widgets
499             sub _do_button_release {
500             my ($widget, $event, $ref_weak_self) = @_;
501             if (my $self = $$ref_weak_self) {
502             if ($event->button == $self->{'button'}) {
503             $self->end ($event);
504             }
505             }
506             return 0; # Gtk2::EVENT_PROPAGATE
507             }
508              
509             sub _maybe_move {
510             my ($self, $widget, $root_x, $root_y) = @_;
511             #### _maybe_move: "@{[$widget||'[undef]']}", $root_x, $root_y
512              
513             $self->{'xy_widget'} = $widget;
514             $self->{'root_x'} = $root_x;
515             $self->{'root_y'} = $root_y;
516              
517             $self->{'sync_call'} ||= do {
518             require Gtk2::Ex::SyncCall;
519             ### new SyncCall on: "$self->{'widgets'}->[0]"
520             if (my $widget = List::Util::first {$_->realized} @{$self->{'widgets'}}) {
521             Gtk2::Ex::SyncCall->sync ($widget,
522             \&_sync_call_handler,
523             Gtk2::Ex::Xor::_ref_weak ($self));
524             } else {
525             $self->signal_emit ('moved', undef, undef, undef);
526             undef;
527             }
528             };
529             }
530             sub _sync_call_handler {
531             my ($ref_weak_self) = @_;
532             my $self = $$ref_weak_self || return;
533             ### CrossHair _sync_call_handler()
534              
535             $self->{'sync_call'} = undef;
536             if (! $self->{'active'}) { return; } # turned off before sync returned
537              
538             _undraw ($self); # erase old
539             _draw ($self); # draw new
540              
541             my ($xy_widget, $x, $y);
542             if ($xy_widget = $self->{'xy_widget'}) {
543             ($x, $y) = @{_pw($self,$xy_widget)}{'x','y'};
544             }
545             $self->signal_emit ('moved', $xy_widget, $x, $y);
546             }
547              
548             sub _do_expose_event {
549             my ($widget, $event, $ref_weak_self) = @_;
550             ### CrossHair _do_expose_event()
551             if (my $self = $$ref_weak_self) {
552             _draw ($self, [$widget], $event->region);
553             }
554             return 0; # Gtk2::EVENT_PROPAGATE
555             }
556              
557             sub _undraw {
558             my ($self, $widgets) = @_;
559             $widgets ||= $self->{'widgets'};
560             ### _undraw(): "@$widgets"
561              
562             my @widgets = grep { exists(_pw($self,$_)->{'x'}) } @$widgets;
563             _draw ($self, \@widgets);
564             foreach my $widget (@widgets) {
565             # position undetermined as well as undrawn
566             delete _pw($self,$widget)->{'x'};
567             }
568             ### _undraw() done
569             }
570              
571             # $widgets is an arrayref of widgets to draw, or undef for all
572             sub _draw {
573             my ($self, $widgets, $clip_region) = @_;
574             $self->{'active'} || return;
575             $widgets ||= $self->{'widgets'};
576             my $root_x = $self->{'root_x'};
577             my $root_y = $self->{'root_y'};
578              
579             foreach my $widget (@$widgets) {
580             ### _draw(): "$widget"
581             my $pw = _pw($self,$widget);
582             my $win = $widget->Gtk2_Ex_Xor_window || next; # perhaps unrealized
583              
584             if (! exists $pw->{'x'}) {
585             ### establish draw position: "$widget", $root_x, $root_y
586             @{$pw}{'x','y'}
587             = (defined $root_x
588             ? Gtk2::Ex::WidgetBits::xy_root_to_widget ($widget, $root_x, $root_y)
589             : ());
590             ### at: $pw->{'x'}, $pw->{'y'}
591             }
592              
593             my $x = $pw->{'x'};
594             if (! defined $x) { next; }
595             my $y = $pw->{'y'};
596              
597             my $gc = ($pw->{'gc'} ||= do {
598             ### create gc
599             my $line_width = $self->get('line_width');
600             my $line_style = $self->{'line_style'} || 'double-dash';
601             Gtk2::Ex::Xor::shared_gc
602             (widget => $widget,
603             foreground_xor => $self->{'foreground'},
604             background => 0, # no change
605             line_width => $line_width,
606             line_style => $line_style,
607             fill => 'stippled',
608             cap_style => 'projecting',
609             ($line_style eq 'solid' ? ()
610             : (dash_list => [ ($line_width || 1) * 4 ])),
611             # subwindow_mode => 'include_inferiors',
612             );
613             });
614              
615             if ($win != $widget->window) {
616             # if the operative Gtk2_Ex_Xor_window is not the main widget window,
617             # then adjust from widget coordinates to the $win subwindow
618             my ($wx, $wy) = $win->get_position;
619             ### subwindow offset: "$wx,$wy"
620             $x -= $wx;
621             $y -= $wy;
622             }
623              
624             my ($x_lo, $y_lo, $x_hi, $y_hi);
625             if ($widget->get_flags & 'no-window') {
626             my $alloc = $widget->allocation;
627             $x_lo = $alloc->x;
628             $x_hi = $alloc->x + $alloc->width - 1;
629             $y_lo = $alloc->y;
630             $y_hi = $alloc->y + $alloc->height - 1;
631             $x += $x_lo;
632             $y += $y_lo;
633             } else {
634             ($x_hi, $y_hi) = $win->get_size;
635             $x_lo = 0;
636             $y_lo = 0;
637             }
638              
639             if ($clip_region) { $gc->set_clip_region ($clip_region); }
640             $win->draw_segments
641             ($gc,
642             $x_lo,$y, $x_hi,$y, # horizontal
643             $x,$y_lo, $x,$y_hi); # vertical
644             if ($clip_region) { $gc->set_clip_region (undef); }
645             }
646             }
647              
648             # 'style-set' signal handler on each widget
649             # A style change normally provokes a full redraw. Think it's enough to rely
650             # on that for redrawing the crosshair against a possible new background, so
651             # just refresh the gc.
652             sub _do_style_set {
653             my ($widget, $prev_style, $ref_weak_self) = @_;
654             ### CrossHair _do_style_set: "$widget"
655             my $self = $$ref_weak_self || return;
656             delete _pw($self,$widget)->{'gc'}; # possible new colour
657             }
658              
659              
660             #------------------------------------------------------------------------------
661             # generic helpers
662              
663             sub _event_root_coords {
664             my ($event) = @_;
665              
666             # Do a get_pointer() to support 'pointer-motion-hint-mask'.
667             # Maybe should use $display->get_state here instead of just get_pointer,
668             # but crosshair and lasso at present only work with the mouse, not an
669             # arbitrary input device.
670             if ($event->can('is_hint')
671             && $event->is_hint
672             && (my $window = $event->window)) {
673             return ($window->get_screen->get_root_window->get_pointer)[1,2];
674             } else {
675             return $event->root_coords;
676             }
677             }
678              
679             # Return true if $x,$y in root window coordinates is within $widget's
680             # allocated rectangle.
681             # FIXME: Would like to exclude parts of $widget which are overlapped by
682             # other widgets and/or windows.
683             #
684             sub _widget_contains_root_xy {
685             my ($widget, $root_x, $root_y) = @_;
686             my ($wx, $wy) = Gtk2::Ex::WidgetBits::xy_root_to_widget ($widget, $root_x, $root_y)
687             or return 0; # $widget unrealized
688             return _widget_contains_xy ($widget, $wx, $wy);
689             }
690              
691             # Return true if $x,$y in widget coordinates is within $widget's allocated
692             # rectangle. The rectangle $widget->allocation gives the size (its x,y
693             # position relative to the windowed parent is ignored).
694             #
695             sub _widget_contains_xy {
696             my ($widget, $x, $y) = @_;
697             ### _widget_contains_xy(): $x,$y
698             return ($x >= 0 && $y >= 0
699             && do {
700             my $alloc = $widget->allocation;
701             $x < $alloc->width && $y < $alloc->height });
702             }
703              
704             # sub _rect_contains_xy {
705             # my ($rect, $x) = @_;
706             # return ($rect->x <= $x
707             # && $rect->y <= $y
708             # && $rect->x + $rect->width >= $x
709             # && $rect->y + $rect->height >= $y);
710             # }
711              
712             # sub _xy_widget_to_root {
713             # my ($widget, $x, $y) = @_;
714             # my ($root_x, $root_y) = Gtk2::Ex::WidgetBits::get_root_position ($widget);
715             # if (! defined $root_x) {
716             # return; # if $widget unrealized
717             # } else {
718             # return ($root_x + $x, $root_y + $y);
719             # }
720             # }
721              
722             # _widget_translate_coordinates_toplevel() is the same as
723             # gtk_widget_translate_coordinates, but allows widgets $src and $dst to be
724             # under different toplevels.
725             #
726             # sub _widget_translate_coordinates_toplevel {
727             # my ($src, $dst, $x, $y) = @_;
728             # if (my @ret = $src->translate_coordinates ($dst, $x, $y)) {
729             # return @ret;
730             # }
731             # require Gtk2::Ex::WidgetBits;
732             # my ($src_x, $src_y) = Gtk2::Ex::WidgetBits::get_root_position ($src);
733             # if (! defined $src_x) {
734             # # $src not realized
735             # return;
736             # }
737             # my ($dst_x, $dst_y) = Gtk2::Ex::WidgetBits::get_root_position ($dst);
738             # if (! defined $dst_x) {
739             # # $dst not realized
740             # return;
741             # }
742             # return ($src_x + $x - $dst_x,
743             # $src_y + $y - $dst_y);
744             # }
745              
746              
747             #------------------------------------------------------------------------------
748              
749              
750             # Not sure about these yet:
751             #
752             # Glib::ParamSpec->enum
753             # ('line-style',
754             # 'line-style',
755             # 'blurb',
756             # 'Gtk2::Gdk::LineStyle',
757             # DEFAULT_LINE_STYLE,
758             # Glib::G_PARAM_READWRITE),
759             #
760             # =item C (default C)
761             #
762             # Attributes for the graphics context (C) used to draw. New
763             # settings here only take effect on the next C. For example,
764             #
765             # $crosshair->{'line_style'} = 'solid';
766              
767              
768              
769             1;
770             __END__